fix: allow admins to approve dependency guard (#88966)

* fix: allow admins to approve dependency guard

* fix: auto-bypass trusted dependency authors
This commit is contained in:
Dallin Romney
2026-06-01 10:17:14 -07:00
committed by GitHub
parent 469bec97ef
commit e3d24faecd
3 changed files with 261 additions and 33 deletions

View File

@@ -5,22 +5,26 @@ import {
createAutoscrubCommit,
dependencyGuardCommentAuthors,
dependencyGuardCommentHeadSha,
dependencyGuardTrustedActorCandidates,
dependencyFieldChanges,
dependencyOverrideExpectedSha,
findDependencyOverrideCommand,
findDependencyOverrideCommandAsync,
findTrustedDependencyGuardActor,
githubApi,
isAutoscrubbedDependencyComment,
isDependencyGuardAuthorizedForHead,
isDependencyFile,
isDependencyGuardMarkerComment,
isDependencyManifest,
isDependencyGuardTrustedForHead,
isPackageLockfile,
readBoundedGitHubErrorText,
renderAuthorizedDependencyComment,
renderAutoscrubbedDependencyComment,
renderBlockedDependencyComment,
renderClearedDependencyGuardComment,
renderTrustedDependencyComment,
sanitizeDisplayValue,
securityApproverSet,
shouldAutoscrubDependencyLockfiles,
@@ -142,6 +146,89 @@ describe("dependency guard script", () => {
).resolves.toBeNull();
});
it("accepts repository admins through the same sha-bound override command", async () => {
const comments = [
{
body: "/allow-dependencies-change admin reviewed",
created_at: "2026-05-28T20:03:00Z",
html_url: "https://example.test/comment",
user: { login: "repo-admin" },
},
];
await expect(
findDependencyOverrideCommandAsync({
comments,
expectedSha: headSha,
isSecurityMember: async (login) => login === "repo-admin",
newerThan: "2026-05-28T20:02:00Z",
}),
).resolves.toEqual({
login: "repo-admin",
reason: "admin reviewed",
sha: headSha,
url: "https://example.test/comment",
});
});
it("recognizes trusted dependency guard actors automatically", async () => {
const sameActorCandidates = dependencyGuardTrustedActorCandidates({
pullRequest: { user: { login: "repo-admin" } },
event: { pull_request: { head: { sha: headSha } }, sender: { login: "repo-admin" } },
currentHeadSha: headSha,
});
const untrustedAuthorCandidate = dependencyGuardTrustedActorCandidates({
pullRequest: { user: { login: "contributor" } },
event: { after: headSha, sender: { login: "security-user" } },
currentHeadSha: headSha,
});
const staleAuthorCandidate = dependencyGuardTrustedActorCandidates({
pullRequest: { user: { login: "repo-admin" } },
event: { pull_request: { head: { sha: staleSha } }, sender: { login: "repo-admin" } },
currentHeadSha: headSha,
});
expect(sameActorCandidates).toEqual([{ login: "repo-admin", source: "pull request author" }]);
expect(untrustedAuthorCandidate).toEqual([
{ login: "contributor", source: "pull request author" },
]);
expect(staleAuthorCandidate).toEqual([]);
await expect(
findTrustedDependencyGuardActor({
candidates: untrustedAuthorCandidate,
isDependencyApprover: async (login) =>
login === "security-user" || login === "repo-admin" ? "openclaw-secops" : null,
}),
).resolves.toBeNull();
await expect(
findTrustedDependencyGuardActor({
candidates: sameActorCandidates,
isDependencyApprover: async (login) => (login === "repo-admin" ? "repository admin" : null),
}),
).resolves.toEqual({
login: "repo-admin",
reason: "pull request author; repository admin",
});
});
it("renders trusted dependency graph comments without blocker language", () => {
const body = renderTrustedDependencyComment({
actor: { login: "repo-admin", reason: "pull request author; repository admin" },
headSha,
});
expect(body).toContain("<!-- openclaw:dependency-graph-guard -->");
expect(body).toContain("Dependency graph changes noted");
expect(body).toContain("informational");
expect(body).toContain("@repo-admin");
expect(body).toContain(headSha);
expect(body).not.toContain("are blocked");
expect(body).not.toContain("/allow-dependencies-change");
expect(isDependencyGuardTrustedForHead({ body }, headSha)).toBe(true);
expect(isDependencyGuardTrustedForHead({ body }, staleSha)).toBe(false);
});
it("rejects override commands without a freshness barrier", () => {
const override = findDependencyOverrideCommand({
comments: [

View File

@@ -221,6 +221,17 @@ describe("dependency guard workflow", () => {
expect(autoscrubCommentIndex).toBeGreaterThan(deleteCommentIndex);
});
it("checks trusted actors before autoscrub can mutate dependency changes", () => {
const script = readFileSync("scripts/github/dependency-guard.mjs", "utf8");
const trustedActorIndex = script.indexOf("const trustedActor =");
const autoscrubCandidateIndex = script.indexOf("const autoscrubCandidate =");
const autoscrubOutputIndex = script.indexOf('await setOutput("autoscrub", "true")');
expect(trustedActorIndex).toBeGreaterThan(0);
expect(autoscrubCandidateIndex).toBeGreaterThan(trustedActorIndex);
expect(autoscrubOutputIndex).toBeGreaterThan(trustedActorIndex);
});
it("requires secops review for future workflow or guard changes", () => {
const codeowners = readFileSync(CODEOWNERS, "utf8");
expect(codeowners).toContain(