Conversation
Add .github/workflows/security-review.yml that runs a security-focused differential review on every PR and fails the check (the gate) on any CRITICAL/HIGH finding at MEDIUM+ confidence, computed deterministically from a verdict JSON via jq. Uses pull_request (not pull_request_target) so fork PRs run read-only. Vendor the Trail of Bits differential-review skill + adversarial-modeler agent into .claude/ (pinned, attributed CC BY-SA 4.0) and un-ignore those paths while keeping local Claude state ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an automated, merge-gating security review workflow that runs on pull requests and vendors the Trail of Bits “differential-review” skill + an adversarial modeling agent into the repo for deterministic, in-repo review content.
Changes:
- Introduces a new
Security Review (gated)GitHub Actions workflow that runs Claude-based differential security review and fails the check based on a jq-evaluated verdict JSON. - Vendors the
differential-reviewskill documentation/assets plus theadversarial-modeleragent under.claude/for in-repo pinning/attribution. - Updates
.gitignoreto ignore local Claude state while attempting to track the vendored.claude/skills/and.claude/agents/content.
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Adjusts ignore rules to (intended) track vendored .claude skills/agents while ignoring local Claude state. |
.github/workflows/security-review.yml |
Adds the gated, per-PR security review workflow and jq-based merge-blocking logic. |
.claude/skills/differential-review/SKILL.md |
Adds the core “differential-review” skill definition and overview. |
.claude/skills/differential-review/reporting.md |
Adds detailed reporting format guidance for the skill. |
.claude/skills/differential-review/patterns.md |
Adds a vulnerability pattern checklist/reference. |
.claude/skills/differential-review/methodology.md |
Adds the phase-by-phase differential review methodology. |
.claude/skills/differential-review/ATTRIBUTION.md |
Adds upstream attribution/license/version pin statement. |
.claude/skills/differential-review/assets/trail-of-bits-mark.svg |
Adds vendored branding asset referenced by the skill. |
.claude/skills/differential-review/agents/openai.yaml |
Adds UI/branding metadata for the skill’s agent interface. |
.claude/skills/differential-review/adversarial.md |
Adds adversarial analysis methodology used for high-risk changes. |
.claude/agents/adversarial-modeler.md |
Adds the adversarial modeling agent definition used by the skill. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Security note: this uses `pull_request` (NOT `pull_request_target`). Fork PRs | ||
| # therefore run with a read-only GITHUB_TOKEN and cannot exfiltrate secrets, at | ||
| # the cost of the inline PR comment being skipped on forks (the gate + job | ||
| # summary still work). Internal PRs get write access and post the review comment. |
There was a problem hiding this comment.
Good catch — fixed in 2d8297c. You're right that pull_request withholds secrets from forked-repo PRs, so the token is empty and the review cannot run there, and the header comment claiming the gate "still works" on forks was wrong.
Changes:
- The review step now has
if: github.event.pull_request.head.repo.full_name == github.repository, so it is skipped (not failed) on fork PRs. - The gate step detects the fork case (
IS_FORK) up front and exits 0 with a::notice+ job-summary explanation, so fork PRs are not blocked. A maintainer can re-run the review from a same-repo branch to get a gated review. - Corrected the header security note to describe this accurately.
| if ! jq -e . "$VERDICT" >/dev/null 2>&1; then | ||
| echo "::error title=Security review invalid::$VERDICT is not valid JSON. Failing closed." | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Agreed — fixed in 2d8297c. jq -e . only checked JSON validity, so {} passed and a missing .findings was silently read as "0 findings" (false pass). The gate now fails closed unless the verdict is an object with a findings array and every entry has a recognized severity (CRITICAL|HIGH|MEDIUM|LOW|INFO) and confidence (HIGH|MEDIUM|LOW) enum:
(type == "object")
and ((.findings | type) == "array")
and (all(.findings[]?;
(.severity as $s | ["CRITICAL","HIGH","MEDIUM","LOW","INFO"] | index($s))
and (.confidence as $c | ["HIGH","MEDIUM","LOW"] | index($c))))This also closes a subtler hole you hinted at: a finding with a bogus/missing severity would have evaded the blocking filter, so the whole verdict is now rejected in that case. A legitimately empty findings: [] (clean review) still passes. Verified across 9 good/bad cases locally.
…kips anthropics/claude-code-action performs "workflow validation": it refuses to run unless this workflow file is byte-identical to the copy on the default branch. That makes the review unable to run on the very PR that introduces or edits it, so the fail-closed gate turned that PR red and unmergeable (chicken-and-egg). Detect that exact condition — workflow file absent from or differing from the default branch — and pass with a notice instead, while still failing closed for genuine post-merge runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address PR review feedback:
- Fork PRs: `pull_request` withholds secrets from forks, so the review
step now skips on forked-repo PRs and the gate passes with a notice
instead of failing closed and blocking every fork PR. Correct the
header comment, which wrongly claimed the gate still works on forks.
- Verdict schema: `jq -e .` only checked JSON validity, so `{}` passed
and a missing `findings` was read as "0 findings" (false pass). Now
require a findings[] array with a valid severity + confidence enum on
every entry, failing closed otherwise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the review @copilot-pull-request-reviewer. Both inline comments were valid and are addressed in 2d8297c:
Note: the |
|
@GTC6244 I'm unable to start working on this because of repository rules that prevent me from pushing to the branch:
See the documentation for more details. |
🤖 Code review (Claude)This PR adds Findings1. Pre-seeded
|
Address the Claude code review on PR #550: - HIGH: a verdict committed into the PR would let the gate read attacker-supplied findings whenever the review step fails/times out. Add a pre-step that refuses a tracked .security-review/ and wipes any working-tree copy, and gitignore .security-review/ so local runs can't commit one. - MEDIUM: the "not gating yet" workflow check compared the writable working tree, which the review step (Write/Edit) could mutate to force the skip path. Compare the committed HEAD copy instead. - LOW: fail loudly if the default branch can't be fetched, instead of silently comparing a stale FETCH_HEAD left by the base-ref fetch. - Nits: scope the .claude gitignore negations to only the vendored skill/agent; escape pipes in model-authored step-summary table cells; drop the unused `counts` object from the prompt schema; document that fork PRs pass with zero automated review once the check is required. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Response to Claude code reviewThanks — genuinely good findings. All three + the nits are addressed in db8fe00. Findings1. Pre-seeded 2. Skip-check compared the working tree, not HEAD (MEDIUM) — fixed. 3. Nits
Not changed
Verified: YAML parses, gitignore scoping, schema validation (9 cases), pipe-escaping, and the fetch/HEAD comparison logic all tested locally before pushing. |
Adds
.github/workflows/security-review.yml, a security review that runs on every PR and gates merge: the job fails on any CRITICAL/HIGH finding at MEDIUM+ confidence, computed deterministically from a verdict JSON via jq (and fails closed if the review produces no verdict). It usespull_requestrather thanpull_request_target, so fork PRs run with a read-only token and can't reach secrets. The Trail of Bitsdifferential-reviewskill and itsadversarial-modeleragent are vendored into.claude/(pinned v1.1.1, attributed CC BY-SA 4.0) so the reviewed content is in-repo with no CI-time supply-chain fetch, and.gitignoreis adjusted to track those paths while keeping local Claude state ignored. To turn this into a true merge blocker, mark thesecurity-reviewcheck as required in branch protection formain.