From e61bb68cd48c337b140e9be830e5edf2f57630de Mon Sep 17 00:00:00 2001 From: John McChesney TenEyck Jr <59268465+jmcte@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:49:14 +0100 Subject: [PATCH] fix: restrict claude comment triggers --- .github/workflows/claude.yml | 18 +++++++++++++++--- test/workflow.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index c22c2fb..68f474d 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -26,9 +26,21 @@ jobs: claude: if: | github.event_name == 'workflow_dispatch' || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) + ( + github.event_name == 'issue_comment' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) && + contains(github.event.comment.body, '@claude') + ) || + ( + github.event_name == 'pull_request_review_comment' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) && + contains(github.event.comment.body, '@claude') + ) || + ( + github.event_name == 'pull_request_review' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) && + contains(github.event.review.body, '@claude') + ) runs-on: - self-hosted - linux diff --git a/test/workflow.test.ts b/test/workflow.test.ts index f3645fa..a2cd75e 100644 --- a/test/workflow.test.ts +++ b/test/workflow.test.ts @@ -275,6 +275,31 @@ describe("CI workflow", () => { expect(workflow.jobs["ci-gate"]["runs-on"]).toEqual(shellSafePublicRunner); }); + test("restricts Claude comment triggers to trusted repository actors", () => { + const workflow = YAML.parse( + fs.readFileSync(path.resolve(".github/workflows/claude.yml"), "utf8") + ) as { + jobs: Record>; + }; + + const claudeJob = workflow.jobs.claude; + const condition = String(claudeJob.if); + + expect(claudeJob["runs-on"]).toEqual(shellSafePublicRunner); + expect(condition).toContain("github.event_name == 'workflow_dispatch'"); + expect(condition).toContain( + "contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)" + ); + expect(condition).toContain( + "contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.review.author_association)" + ); + expect(condition).toContain("contains(github.event.comment.body, '@claude')"); + expect(condition).toContain("contains(github.event.review.body, '@claude')"); + expect(condition).not.toContain("FIRST_TIME_CONTRIBUTOR"); + expect(condition).not.toContain("CONTRIBUTOR"); + expect(condition).not.toContain("NONE"); + }); + test("renders the Linux Docker contract on shell-safe self-hosted Linux", () => { const workflow = YAML.parse( fs.readFileSync(path.resolve(".github/workflows/ci.yml"), "utf8")