diff --git a/.github/workflows/security-review.yml b/.github/workflows/security-review.yml index 61e5b30d3c20..35dda783c2f4 100644 --- a/.github/workflows/security-review.yml +++ b/.github/workflows/security-review.yml @@ -55,7 +55,7 @@ jobs: cancel-in-progress: false permissions: contents: read - pull-requests: read + pull-requests: write actions: read issues: write statuses: write diff --git a/scripts/github/guard-shared.mjs b/scripts/github/guard-shared.mjs index 04906782c375..9c09408068f3 100644 --- a/scripts/github/guard-shared.mjs +++ b/scripts/github/guard-shared.mjs @@ -135,7 +135,9 @@ export function createIssueMutationHelpers({ }) { const ignoreUnavailableWritePermission = (action) => (error) => { if (error?.status === 403) { - warn(`Skipping ${action}; token does not have write permission.`); + warn( + `Skipping ${action}; GitHub API rejected the request: ${sanitizeGuardDisplayValue(error.message)}`, + ); return; } if (error?.status === 404 || error?.status === 422) { diff --git a/test/scripts/security-review-workflow.test.ts b/test/scripts/security-review-workflow.test.ts index 2b56453c00df..cf68bc56d97e 100644 --- a/test/scripts/security-review-workflow.test.ts +++ b/test/scripts/security-review-workflow.test.ts @@ -43,7 +43,7 @@ function readWorkflow(name: string): Workflow { const reviewPermissions = { contents: "read", - "pull-requests": "read", + "pull-requests": "write", actions: "read", issues: "write", statuses: "write", diff --git a/test/scripts/security-sensitive-guard-script.test.ts b/test/scripts/security-sensitive-guard-script.test.ts index 09db3cf18948..af8759aa0661 100644 --- a/test/scripts/security-sensitive-guard-script.test.ts +++ b/test/scripts/security-sensitive-guard-script.test.ts @@ -171,6 +171,25 @@ describe("security-sensitive guard entry point", () => { expect(result.comment).toContain("Informational"); }); + it.each([ + { script: "security-sensitive-guard" as const, filename: "src/gateway/auth.ts" }, + { script: "dependency-guard" as const, filename: "pnpm-workspace.yaml" }, + ])("$script reports GitHub errors when notice writes are forbidden", ({ script, filename }) => { + const result = runGuard({ + script, + files: [{ filename }], + authorRole: "maintain", + comments: [], + routes: { + "POST /repos/openclaw/openclaw/issues/7/comments": { httpError: 403 }, + "POST /repos/openclaw/openclaw/issues/7/labels": { httpError: 403 }, + }, + }); + expect(result.status, result.stderr).toBe(0); + expect(result.stderr).toMatch(/Skipping label .*Fixture API failure/u); + expect(result.stderr).toMatch(/Skipping comment creation.*Fixture API failure/u); + }); + it("does not transfer a maintainer author's exemption to a duplicate PR with the same head", () => { const duplicatePullRequest = { number: 8,