security-audit
GitHub用于在发布或合并前审查代码变更,检测恶意代码、后门及严重安全回归。涵盖供应链、数据外泄、注入等风险,通过快速扫描与深度子代理审查结合,确保变更安全性。
Trigger Scenarios
Install
npx skills add rommapp/romm --skill security-audit -g -y
SKILL.md
Frontmatter
{
"name": "security-audit",
"description": "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 withgit tag+ tag dates, don't assume. - PR/branch:
git merge-base master HEAD..HEAD(diff against the merge base, not rawmaster..HEAD). - A remote PR you don't have locally: fetch the ref first (
git fetch origin pull/<n>/headorgh pr checkout <n>), then set$RANGEas above so every sweep runs against it.gh pr diff <n>alone only prints the diff and leaves$RANGEunset, so thegit diff $RANGEsweeps would fall back to the working tree, so don't rely on it.
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):
git diff $RANGE -- . ':(exclude)*.lock' ':(exclude)**/tests/**' ':(exclude)**/__generated__/**' \
| grep -E '^\+' | grep -vE '^\+\+\+' \
| grep -oiE 'https?://[a-z0-9.:/_-]+' | sed -E 's#https?://##; s#/.*##' \
| sort | uniq -c | sort -rn
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):
git diff $RANGE | grep -E '^\+' | grep -vE '^\+\+\+' \
| grep -iE 'eval\(|new Function|exec\(|os\.system|subprocess|shell=True|child_process|atob\(|fromCharCode|base64|pickle\.loads|yaml\.unsafe|innerHTML|v-html|document\.write|createElement\(.script'
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.
Dependencies / supply chain:
git diff $RANGE -- '**/package.json' '**/pyproject.toml' 'uv.lock' '**/package-lock.json' '**/requirements*.txt'
- New deps must come from the official registry. Grep the lockfile diff for
git+, non-pythonhosted/pypi.org/ non-registry.npmjs.orgURLs, 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:
git diff $RANGE -- .github/ '**/Dockerfile' '**/*entrypoint*.sh' docker/ examples/*.yml
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):
git diff --name-status $RANGE | grep -E '^A' | grep -iE '\.(bin|so|dll|exe|wasm|node|png|jpg|gif|zip|gz|woff2?)$'
Commit provenance:
git log --format='%h A:%ae C:%ce %s' $RANGE | sort # author vs committer per commit
git shortlog -sne $RANGE # distinct authors + counts
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_routeor with weakened scope/role/ownership checks; removedassert_*_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. - Frontend — off-app
fetch/sendBeacon/Image().src/WebSocket; token/cookie/localStorage reads sent anywhere off-origin;eval/Function/v-html/innerHTML/dynamic<script>; external redirects; iframesrc/postMessagetrust. - Supply chain & provenance — deps, lockfile sources, CI/infra, binaries, commit authorship (section 2, done thoroughly).
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. "enablepinact run --checkin 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.
Version History
- 42e8043 Current 2026-08-20 16:50


