Audit a diff (a release tag range or a PR branch) for anything malicious or a serious security regression before shipping/merging. Use when asked to "check this release", "make sure nothing malicious slipped in", "security review this PR", or vet upstream/contributor changes. Covers supply chain, egress/exfiltration, auth/injection/traversal, CI/infra, and commit provenance — with a fast direct pass plus fanned-out deep review. Trigger on any "is this change safe?" request over a diff.
Audit a diff (a release tag range or a PR branch) for anything malicious or a serious security regression before shipping/merging. Use when asked to "check this release", "make sure nothing malicious slipped in", "security review this PR", or vet upstream/contributor changes. Covers supply chain, egress/exfiltration, auth/injection/traversal, CI/infra, and commit provenance — with a fast direct pass plus fanned-out deep review. Trigger on any "is this change safe?" request over a diff.
Security Audit of a Diff
Goal: decide whether a set of changes contains anything malicious (backdoor, exfiltration, injection, obfuscated payload, supply-chain tamper) or a serious security regression (removed auth/ownership check). Assume most of it is legitimate work — your job is to find the needle if there is one, and give an honest verdict, not a vibe.
Two speeds, both required for anything non-trivial:
Direct pass (you, fast) — cheap high-signal sweeps that catch the obvious and scope the surface.
Fanned-out deep review (parallel subagents) — one per surface, reading actual files, when the diff is large.
Don't skip the direct pass even when delegating — it tells you where the risk lives and cross-checks the agents.
1. Establish the range
Release: previous tag .. new tag. Confirm the true predecessor with git tag + tag dates, don't assume.
PR/branch: git merge-base master HEAD..HEAD (diff against the merge base, not raw master..HEAD).
A remote PR you don't have locally: fetch the ref first (git fetch origin pull/<n>/head or gh pr checkout <n>), then set $RANGE as above so every sweep runs against it. alone only prints the diff and leaves unset, so the sweeps would fall back to the working tree, so don't rely on it.
gh pr diff <n>
$RANGE
git diff $RANGE
RANGE=5.0.0..5.1.0-alpha.1 # or "$(git merge-base master HEAD)..HEAD"
git rev-list --count $RANGE# how many commits
git diff --stat$RANGE | tail -30 # size + what area moved
Also pull the human context (release notes / PR description) — new env vars, new endpoints, and new deps named there tell you what to scrutinize.
2. Direct pass — high-signal sweeps
Run these yourself. The single most important signal for "malicious" is unexpected outbound destinations.
Egress — every new host/IP/domain in added lines (exclude lockfiles/tests/generated to cut noise):
Every hostname must resolve to a known-legit provider, the app's own origin, or a config-example placeholder. An unrecognized domain/raw IP is the finding — chase it.
Code-exec / obfuscation in added lines (ignore test files):
Base64/hex blobs, eval(atob(...)), hand-obfuscated strings = stop and dig. Subprocess/create_subprocess_exec hits: confirm they're shell=False, list-form args, and that no user-controlled value reaches the command.
New deps must come from the official registry. Grep the lockfile diff for git+, non-pythonhosted/pypi.org / non-registry.npmjs.org URLs, alternate index URLs — any of those is a red flag.
Watch for typosquats (name one char off a popular package) and unexpected new transitive sources.
CI / infra — these run with secrets, so they're a prime exfil vector:
Flag: pull_request_target (especially with a checkout of PR code), newly referenced secrets.*, curl|bash / wget|sh to unknown hosts, Actions repinned to a fork or downgraded to a mutable tag, changed package registries/mirrors, new privileged/host mounts. SHA-pinning actions is a good sign.
New binaries/blobs (source repos rarely need them):
Every author should be a plausible contributor. author≠committer with committer noreply@github.com is normal (squash-merge/bot). An unknown human committer overriding another author's commit is not.
3. Fan out deep review
Do this for any non-trivial diff. The direct pass alone only catches what its patterns match, so a regression that fits no grep still needs eyes on the actual files. Scale the effort to the diff: a big (hundreds of files) or sensitive-surface change wants one subagent per surface; a small non-trivial diff still gets a file-level read (yourself or a single agent) beyond the sweeps. Spawn parallel subagents, one per surface, in a single message. Each reads the actual files, not just the diff, and returns findings ranked by severity + an explicit clean / needs-attention / malicious verdict. Give each the exact $RANGE and a scoped file glob.
Typical split:
Backend — new/changed endpoints missing @protected_route or with weakened scope/role/ownership checks; removed assert_*_visible; command/SQL injection; path traversal in new file ops (delete/upload/download endpoints are prime); SSRF where a request URL is user-influenced; secret handling.
Prompt each agent to: assume legit-until-proven, cite file:line, say concretely whether each concern is exploitable or benign, and not to pad the report. See docs/BACKEND_ARCHITECTURE.md for the auth/scope model when judging backend changes.
4. Judge, then verdict
For each candidate finding, decide reachability before calling it: Is the input attacker-controlled or admin-config/DB-derived? Is the endpoint authed and scoped? Does the tainted value actually reach a sink? A scary-looking sink fed only by trusted server-side data is benign — say so.
Deliver one consolidated verdict:
What you checked and cleared — grouped by surface, so the reader sees coverage, not just a green light.
Findings — ranked by severity, each with file:line, what it does, and why it is/ isn't exploitable.
Overall:clean / needs attention / malicious, plus any non-blocking belt-and-suspenders follow-ups (e.g. "enable pinact run --check in CI").
Be honest about coverage limits: if you verified SHA pins by publisher but didn't resolve them over the network, or sampled rather than read every file, say so.
Anti-patterns
Declaring "clean" from grep alone on a large diff — grep scopes risk, it doesn't clear it. Read the files at the sinks.
Pasting raw diff dumps or agent transcripts back to the user instead of a judged summary.
Treating a subprocess/urlopen/iframe as a finding without tracing whether user input reaches it.
Ignoring CI/infra because "it's not app code" — it's where secrets leak.
Skipping commit-authorship review — a malicious commit can hide among legitimate ones.