Files
pragent/.opencode/skills/security-lens/SKILL.md
Marcos 30d2a3d7da feat(factory): five review skills + a per-review cost model
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
2026-08-18 04:53:49 +00:00

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: subprocess with shell=True, backticks, exec, system on 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: none accepted, 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 (not safe_load), Java native deserialization, unserialize, eval/Function on 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, |safe on input.
  • CSRF protection removed or an endpoint switched from POST to GET.
  • CORS widened to * alongside credentials.
  • An open redirect from a next/return_to parameter.

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.