30d2a3d7da
Skills — the primary now loads conditionally (each one is input tokens), per a load table in pragent.md: - attention-tiering: classify every PR trivial/lite/full/oversized BEFORE reading anything, and cap file reads, linter runs and subagent fan-out per tier. This is the cost governor; the other skills defer to its budget. - linter-playbook: per-ecosystem detect-and-run commands scoped to changed files, the never-install rule, and how to turn a diagnostic into a finding instead of pasting tool output. - security-lens: the inline security checklist for when @security isn't worth delegating, built around a source -> sink test each finding must pass. - malicious-change: hostile-PR detection — injection aimed at the reviewer, install/CI-time hooks, obfuscated payloads, dependency confusion, logic backdoors. Complements the runtime containment added in the previous commit: that stops the agent being hijacked, this makes it report the attempt. - comment-craft: how to write problem/fix/suggestion so a maintainer can act in one read, and what to cut. pilot/cost_model.py — prices a review against published Claude and OpenAI rates (fetched 2026-08-18). Prompt sizes are measured from the factory files rather than guessed; per-tier workloads come from the tiering budgets. The model is explicit about the thing that actually dominates an agent loop: the whole conversation is resent every step, so caching moves ~2.3x of the bill. Blended over a 5/35/55/5 mix with caching on: ~$0.61/PR on Opus 5 or GPT-5.6 Sol, ~$0.24 on Sonnet 5 or Terra, ~$0.12 on Haiku 4.5, ~$0.02 on Luna. At 350 PRs/month that's ~$212 / ~$85 / ~$43 / ~$8.50. Tests: 101 -> 122. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B11e8TZZxJyzHW7jj7KWUN
4.7 KiB
4.7 KiB
name, description
| name | description |
|---|---|
| security-lens | Security review checklist for a PR diff — injection, authn/authz, secrets, crypto, SSRF/path traversal, deserialization — with the data-flow test each finding must pass. Load when the diff touches a risk path and no @security subagent was delegated. |
Security lens
The @security subagent exists for large or heavily security-sensitive diffs.
On lite and most full PRs you do this inline — the fan-out isn't worth it.
This is that checklist.
The test every finding must pass
Before reporting, establish a source → sink path:
- source — where attacker-influenced data enters (request param, header, body, uploaded filename, env in a multi-tenant context, DB row that a user wrote, webhook payload, PR/issue text)
- sink — where it does something (SQL string, shell, filesystem path, HTTP request, deserializer, template, redirect, HTML)
- no effective sanitizer between them — check for one; grep the helper it calls. "There might be no validation" is not a finding.
If you cannot name the source and the sink, you have a code smell, not a
vulnerability. Report it at low or not at all. A false critical costs more
trust than a missed low costs risk.
Checklist
Injection
- SQL/NoSQL built by concatenation or f-string/template interpolation → must be
parameterized. An ORM
raw()/literal()call is the usual escape hatch. - Shell:
subprocesswithshell=True, backticks,exec,systemon anything derived from input. Argument-array form is the fix. - Template injection (Jinja/ERB/Handlebars rendering a user-supplied template, not just user data).
- LDAP, XPath, header injection (CRLF in a redirect/
Set-Cookie).
AuthN / AuthZ
- A new endpoint/handler/route with no auth decorator/middleware its siblings have. Grep a neighbouring route to see the house pattern.
- Authorization that checks authentication only — "is logged in" where it needs "owns this record". IDOR: an object fetched by an id from the request with no ownership predicate.
- Secret/token/password compared with
==→ constant-time compare. - Session/JWT: missing expiry check,
alg: noneaccepted, signature verify skipped, token in a URL or a log line.
Secrets
- Literal keys, passwords, private keys, connection strings in the diff — even in tests or fixtures, if they're real.
- Secrets reaching logs, error strings, telemetry, or a URL query.
- A secret added to a client-side bundle or a container image layer.
Crypto
- MD5/SHA1 for anything security-bearing; unsalted password hashing (needs bcrypt/scrypt/argon2).
Math.random()/random.random()for tokens, IDs, or nonces → CSPRNG.- Hand-rolled crypto, ECB mode, a static/reused IV, a hardcoded salt.
SSRF / path traversal / upload
- A URL from input fetched server-side with no allow-list → SSRF (cloud metadata endpoints are the classic target).
- A path built from input reaching the filesystem with no containment check.
A
..check alone isn't enough — the resolved path must be verified inside the intended root. - Archive extraction without a traversal/symlink check (zip-slip / tar-slip).
- Uploads trusted by client-supplied filename or
Content-Type.
Deserialization & parsing
pickle,yaml.load(notsafe_load), Java native deserialization,unserialize,eval/Functionon input.- XML without external-entity handling disabled (XXE).
Supply chain & CI
- A new dependency: is the name plausible, or a typosquat of a known package?
- A pinned version loosened to a range, or an integrity hash dropped.
- CI changes: a workflow gaining access to secrets on an untrusted trigger, a third-party action pinned to a mutable tag rather than a SHA, a step that runs PR-authored code in a privileged context.
Web
- Reflected/stored XSS:
innerHTML,dangerouslySetInnerHTML,v-html,|safeon input. - CSRF protection removed or an endpoint switched from POST to GET.
- CORS widened to
*alongside credentials. - An open redirect from a
next/return_toparameter.
Severity for security findings
critical— exploitable now by an unauthenticated or low-privileged actor: injection with a reachable sink, auth bypass, a live secret, RCE.high— exploitable with a precondition (a specific role, a race, a non-default config), or a secret in a log.medium— real weakness, no demonstrated path: missing defence in depth, weak crypto not currently load-bearing.low— hardening.
Cite an authoritative URL in reference when the finding turns on a specific
CVE or a documented library footgun. Don't cite a generic OWASP page for a
generic point, and never invent a URL.