Skip to content

fix(ci): harden PR-review workflows (adversarial audit) + polish - #4

Merged
travisccook merged 2 commits into
mainfrom
feat/claude-review-hardening
Jul 6, 2026
Merged

fix(ci): harden PR-review workflows (adversarial audit) + polish#4
travisccook merged 2 commits into
mainfrom
feat/claude-review-hardening

Conversation

@travisccook

Copy link
Copy Markdown
Owner

Hardens the two Claude review workflows after a multi-agent adversarial security audit, plus polish from the self-review on PR #2.

Security (HIGH):

  • Remove Bash(find:*) from both workflows — find -exec sh -c '...' is a single command (no shell operator to split on), so it was a full arbitrary-code-execution escape. On the secret-bearing mention path (untrusted fork content) that was a real token-exfiltration vector.
  • Minimize the mention-path Bash surface to gh pr view/diff/checks/comment + read-only git; drop cat/grep/jq/ls (could read /proc or .git/config).
  • persist-credentials: false on the mention checkout so GITHUB_TOKEN isn't written into .git/config.

Polish (from PR #2's self-review):

  • Interpolate github.event.pull_request.number/github.repository into the post command.
  • Add a concurrency guard (cancel stacked runs).
  • Drop the unused Bash(npm run lint) grant.

Spec's security section updated with the audit findings + residual-risk note.

travisccook and others added 2 commits July 6, 2026 17:18
…rency, drop unused lint grant

The automated review of this PR flagged: (1) the gh pr comment target relied on
implicit gh auto-detection — now interpolate github.event.pull_request.number and
github.repository explicitly; (2) no concurrency guard let rapid pushes stack review
runs — add cancel-in-progress; (3) Bash(npm run lint) was allowlisted but never used.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A multi-agent audit confirmed the 'restricted' allowlist wasn't:
- Bash(find:*) is arbitrary code execution (find -exec sh -c '...' is one command,
  no shell operator to split on) — removed from BOTH workflows. On the secret-bearing
  mention path (untrusted fork content) this was a real token-exfiltration vector.
- Mention path Bash surface minimized to gh pr view/diff/checks/comment + read-only
  git; dropped cat/grep/jq/ls (could read /proc or .git/config via prefix match).
- persist-credentials: false on the mention checkout so GITHUB_TOKEN isn't written
  into .git/config where a read primitive could recover it.
- Documented the residual risk + the COLLABORATOR-gate nit in the spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@travisccook
travisccook merged commit 3f54bde into main Jul 6, 2026
2 checks passed
@travisccook
travisccook deleted the feat/claude-review-hardening branch July 6, 2026 21:22
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Automated review

Scope note: this PR touches only CI workflow config (.github/workflows/claude-mention.yml, .github/workflows/claude-review.yml) and a design doc (docs/superpowers/specs/2026-07-06-automated-pr-review-design.md). No files under libraries/boards/, libraries/manifest.json, or src/*.js changed, so items 2–4 of the standard checklist (board-contribution rules, safety-class labeling, engine/UI invariants) are not applicable to this diff.

1. Checks

Ran locally against the merged PR head (4a9b8ed):

  • npm ci — ✅ pass (272 packages, 1 moderate audit advisory, pre-existing/unrelated to this PR)
  • npm run validate — ✅ pass, all 9 boards + manifest + cross-file checks green
  • npm test — ✅ pass, 7 suites / 136 tests

None of these exercise the changed files directly (they're GitHub Actions YAML), but they confirm the PR doesn't regress the engine/board pipeline.

2–4. Board rules / safety labeling / src invariants

N/A — no board JSON or src/*.js changed this PR.

Review of the actual change (workflow hardening)

The stated fixes all check out against the diff:

  • Bash(find:*) removed from both claude-mention.yml and claude-review.yml. Correct call — find -exec sh -c '...' \; is a single command string with no shell operator to split on, so a naive prefix-based allowlist can't stop it; this was a real arbitrary-exec hole on the untrusted-fork-comment path.
  • claude-mention.yml (.github/workflows/claude-mention.yml:62-64): Bash surface trimmed to git diff/log/show (read-only, scoped to repo objects) + gh pr view/diff/checks/comment. Dropping cat/grep/jq/ls/head/tail/wc and git ls-tree/cat-file/rev-parse/status is a reasonable minimization since those generic read primitives can reach .git/config or /proc via prefix match, whereas git show/git diff are constrained to committed repo content.
  • persist-credentials: false added to the mention-path checkout (claude-mention.yml:36-41) — correct, and doesn't break anything downstream since gh/the action's own token handling don't depend on the git credential helper for read-only git diff/log/show.
  • concurrency group added to claude-review.yml:11-15 to cancel superseded runs — reasonable quota hygiene, no correctness concerns (worst case a cancelled run posts no comment, not a partial/garbled one).
  • PR number/repo now interpolated into the gh pr comment example in the prompt (claude-review.yml:56) instead of the old placeholder-only text — nice clarity fix, matches what I in fact used to post this comment.

One inconsistency worth a follow-up (not a blocker): persist-credentials: false was added only to claude-mention.yml's checkout, not to claude-review.yml's (.github/workflows/claude-review.yml:30-32, still defaults to persist-credentials: true). claude-review.yml keeps cat/grep/jq in its allowlist, so on that path a persisted GITHUB_TOKEN in .git/config is technically readable by the same class of primitive the mention-path hardening was designed to close off. Risk here is lower — claude-review.yml only runs for same-repo (non-fork) PRs from people who already have push access, and the leaked token is just the short-lived Actions token already scoped to what the workflow can do via gh anyway — but for consistency with the stated threat model ("prompt injection in reviewed content must not reach tokens"), consider adding persist-credentials: false there too.

Residual/open question, not necessarily a bug: the audit's find finding was specifically about a command whose arguments (-exec) smuggle execution past a prefix-based allowlist with no shell operator to split on. The same class of question applies to gh pr comment --body "$(...)" — command substitution inside an allowed command's argument string. I can't verify from here whether claude-code-action's permission matcher parses/rejects embedded substitution in the argument list the way it presumably splits on ;/&&/|. Worth a quick confirmation (or an explicit note in the spec doc alongside the other documented residual risks) so this doesn't quietly reopen the same class of hole the PR just closed for find.

Overall: the hardening is well-reasoned, matches its own description, and the fixes are precise (not overbroad — read-only git and gh pr subcommands needed for review are preserved). Advisory only, no blocking issues found.

travisccook added a commit that referenced this pull request Jul 6, 2026
…cher semantics (#6)

Follow-ups from PR #4's own automated review:
- Add persist-credentials:false to claude-review.yml's checkout for consistency with
  the mention path (low risk on the trusted in-repo path, but keeps the invariant).
- Document in the spec why find -exec is a bypass (single command word-list) while
  command substitution / chaining is NOT (the matcher parses those and requires each
  segment to be allowlisted).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant