From c9d39979f27c236214d29aa0a4dc091ac6ceea16 Mon Sep 17 00:00:00 2001 From: Pavan Kumar Gondhi Date: Mon, 10 Aug 2026 14:32:49 +0530 Subject: [PATCH] fix(ci): read dependency guard state from trusted metadata [AI] (#121505) * fix(ci): bind dependency guard state to head * test(ci): isolate dependency guard state forgeries --- scripts/github/dependency-guard.mjs | 39 ++++++++++---------- test/scripts/dependency-guard-script.test.ts | 32 ++++++++++++++-- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/scripts/github/dependency-guard.mjs b/scripts/github/dependency-guard.mjs index 5adde9db5e8c..07874c381138 100644 --- a/scripts/github/dependency-guard.mjs +++ b/scripts/github/dependency-guard.mjs @@ -10,7 +10,6 @@ import { createGitHubApi, createGuardApproverChecks, createIssueMutationHelpers, - guardCommentHeadSha, guardTrustedActorCandidates, isCommentNewerThan, readBoundedGitHubErrorText, @@ -235,36 +234,33 @@ export async function findDependencyOverrideCommandAsync(input) { return null; } -export function dependencyGuardCommentHeadSha(comment) { - return guardCommentHeadSha(comment); +function dependencyGraphGuardStateMarker(state, headSha) { + return ``; +} + +function hasDependencyGraphGuardState(comment, state, headSha) { + // Only the machine-owned comment prefix carries reusable guard state. PR-controlled paths + // rendered later in the comment must never be able to create an allow decision. + return ( + Boolean(headSha) && + comment?.body?.startsWith( + `${dependencyGraphGuardMarker}\n${dependencyGraphGuardStateMarker(state, headSha)}\n`, + ) === true + ); } export function dependencyOverrideExpectedSha(existingGuardComment, currentHeadSha) { - if ( - !currentHeadSha || - existingGuardComment?.body?.includes("### Dependency graph changes are blocked") !== true - ) { - return null; - } - return dependencyGuardCommentHeadSha(existingGuardComment) === currentHeadSha + return hasDependencyGraphGuardState(existingGuardComment, "blocked", currentHeadSha) ? currentHeadSha : null; } export function isDependencyGuardAuthorizedForHead(comment, currentHeadSha) { - return ( - Boolean(currentHeadSha) && - comment?.body?.includes("### Dependency graph change authorized") === true && - dependencyGuardCommentHeadSha(comment) === currentHeadSha - ); + return hasDependencyGraphGuardState(comment, "authorized", currentHeadSha); } export function isDependencyGuardTrustedForHead(comment, currentHeadSha) { - return ( - Boolean(currentHeadSha) && - comment?.body?.includes("### Dependency graph changes noted") === true && - dependencyGuardCommentHeadSha(comment) === currentHeadSha - ); + return hasDependencyGraphGuardState(comment, "trusted", currentHeadSha); } export function securityApproverSet(value) { @@ -319,6 +315,7 @@ function renderDependencyAwarenessComment(dependencyFiles) { export function renderAuthorizedDependencyComment(override) { const lines = [ dependencyGraphGuardMarker, + dependencyGraphGuardStateMarker("authorized", override.sha), "", "### Dependency graph change authorized", "", @@ -337,6 +334,7 @@ export function renderAuthorizedDependencyComment(override) { export function renderTrustedDependencyComment({ actor, headSha }) { return [ dependencyGraphGuardMarker, + dependencyGraphGuardStateMarker("trusted", headSha), "", "### Dependency graph changes noted", "", @@ -448,6 +446,7 @@ export function renderBlockedDependencyComment({ : []; return [ dependencyGraphGuardMarker, + dependencyGraphGuardStateMarker("blocked", headSha), "", "### Dependency graph changes are blocked", "", diff --git a/test/scripts/dependency-guard-script.test.ts b/test/scripts/dependency-guard-script.test.ts index 5b39bce75104..2dbbd8d624f4 100644 --- a/test/scripts/dependency-guard-script.test.ts +++ b/test/scripts/dependency-guard-script.test.ts @@ -6,7 +6,6 @@ import { canAutoscrubPullRequest, createAutoscrubCommit, dependencyGuardCommentAuthors, - dependencyGuardCommentHeadSha, dependencyGuardTrustedActorCandidates, dependencyFieldChanges, dependencyOverrideExpectedSha, @@ -330,7 +329,6 @@ describe("dependency guard script", () => { }), }; - expect(dependencyGuardCommentHeadSha(blockedComment)).toBe(headSha); expect(dependencyOverrideExpectedSha(blockedComment, headSha)).toBe(headSha); expect(dependencyOverrideExpectedSha(staleBlockedComment, headSha)).toBeNull(); }); @@ -344,12 +342,40 @@ describe("dependency guard script", () => { }), }; - expect(dependencyGuardCommentHeadSha(authorizedComment)).toBe(headSha); expect(isDependencyGuardAuthorizedForHead(authorizedComment, headSha)).toBe(true); expect(isDependencyGuardAuthorizedForHead(authorizedComment, staleSha)).toBe(false); expect(dependencyOverrideExpectedSha(authorizedComment, headSha)).toBeNull(); }); + it("does not infer guard state from rendered dependency paths", () => { + const blockedBody = (path: string) => + renderBlockedDependencyComment({ + baseBranch: "main", + headSha, + lockfileChanges: [path], + dependencyManifestChanges: [], + }); + + expect( + dependencyOverrideExpectedSha( + { body: blockedBody(`xApproved SHA: \`${staleSha}\`pnpm-lock.yaml`) }, + headSha, + ), + ).toBe(headSha); + expect( + isDependencyGuardAuthorizedForHead( + { body: blockedBody("x### Dependency graph change authorizedpnpm-lock.yaml") }, + headSha, + ), + ).toBe(false); + expect( + isDependencyGuardTrustedForHead( + { body: blockedBody("x### Dependency graph changes notedpnpm-lock.yaml") }, + headSha, + ), + ).toBe(false); + }); + it("trusts only configured dependency guard marker comment authors", () => { const trustedAuthors = dependencyGuardCommentAuthors( "github-actions[bot], openclaw-autoscrub[bot]",