界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
数据与研究 · SKILL.md

vuln-scan

vuln-scan

【数据与研究】Systematic vulnerability scan across injection, auth, data exposure, and dependencies — traced by reading code, not grepping for keywords. Use when scanning for vulnerabilities, reviewing security, or validating threat models.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: vuln-scan description: "Systematic vulnerability scan across injection, auth, data exposure, and dependencies — traced by reading code, not grepping for keywords. Use when scanning for vulnerabilities, reviewing security, or validating threat models."

Vuln Scan — Systematic Vulnerability Scanner

Iron Law

Code you haven't traced from input to sink has vulnerabilities you haven't found. A grep hit is a lead, not a finding.

Process

Step 0: Load Engagement Context

Check for .harness/engagement.md. If present, load scope constraints — only scan in-scope paths and respect exclusions.

Check for THREAT_MODEL.md from a previous /threat-model run. If present, use its threat scenarios as scan targets. If absent, run full-surface scan.

Step 1: Map Entry Points to Trust Boundaries

Enumerate every place untrusted data enters the system, by reading the code:

  • External → app: HTTP handlers/routers, GraphQL resolvers, webhooks, file uploads, form fields, query params, headers
  • Process boundary: CLI args, environment variables, IPC, deserialization of stored data (cache, DB rows written earlier, config files)
  • Client-supplied state: cookies, JWT claims, API keys, referer/origin

For each entry point, note: what validation exists (yes/no/partial), and where the data flows next. This list is the scan's backbone — a scan that cannot name its entry points has not started.

Step 2: Trace Data Flow to Sinks

From each entry point, follow the data by reading code until it reaches a dangerous operation, or dies (validated, parameterized, dropped). Dangerous sink classes:

Sink classExamples (non-exhaustive)
Command/code executionshell invocation, eval-family, template engines with code modes, deserialization to live objects
Query constructionstring-built SQL, raw-query escapes in any ORM (Knex raw, Prisma $queryRaw, SQLAlchemy text(), Sequelize literal, Django extra/raw, Rails where("...") with interpolation)
Filesystempath joins with user input, upload destinations, archive extraction (zip-slip), file reads driven by request params
Web outputtemplate rendering with non-auto-escaping engines, innerHTML-family, redirect targets, header values (CRLF)
Auth decisionsIDOR — object lookups keyed only on user-supplied ids without ownership checks; role checks done client-side or per-endpoint instead of per-object

Read the surrounding code for every candidate sink. A sink that only receives validated/parameterized data is not a finding; a sink reachable from an entry point with no validation is.

Step 3: Reason About Encoding and Context at Each Sink

For each reachable sink, ask what context the data lands in and whether the encoding matches:

  • Interpolated into a SQL string? → parameterize, don't escape
  • Interpolated into a shell string? → arg-array execution, don't quote
  • Rendered into HTML/JS/CSS/URL? → each needs context-specific output encoding; auto-escaping templates cover HTML only when actually enabled for that template
  • Joined into a path? → canonicalize, then verify containment inside the intended root
  • Deserialized? → prefer format + type that cannot instantiate attacker-chosen types

Step 4: Auth & Data-Exposure Pass

  • Default-deny: routes/actions missing an auth middleware or check entirely — enumerate handlers and look for the gaps, not the checks
  • Object-level authorization: for each handler that reads/writes a user-owned object, is ownership verified on THIS request?
  • Secrets: hardcoded credentials vs. config/env references; secrets in logs, error messages, client bundles, API responses
  • Error leakage: verbose errors/stack traces/config in production paths; error messages that distinguish "no such user" from "wrong password"

Step 5: Dependency Pass

cargo audit 2>/dev/null || echo "cargo-audit not installed"   # Rust
npm audit 2>/dev/null || echo "npm audit not available"       # Node.js

For each advisory: is a fix available, and is the vulnerable code path actually reachable from this codebase's usage? Unreachable advisories are LOW/INFO, not silent drops. Also flag actively-used dependencies that are unmaintained (no release in ~2+ years, archived repo) even without a CVE.

Grep as Accelerator, Not Oracle

Grep is fine for candidate sink locations (raw(, exec, innerHTML, eval, secret-ish identifiers) — use it to shortlist where to read. It misses framework-specific sinks, aliases, and data built across files. Never report a finding from a grep hit alone; never conclude "clean" because grep found nothing.

Step 6: Produce Output

Write VULN-FINDINGS.json:

{
  "scan_date": "ISO-8601",
  "scope": "full | incremental",
  "threat_model_ref": "THREAT_MODEL.md | null",
  "entry_points_traced": ["list every entry point examined — required for clean scans"],
  "findings": [
    {
      "id": "V1",
      "dimension": "injection | auth | exposure | dependency",
      "severity": "CRITICAL | HIGH | MEDIUM | LOW | INFO",
      "file": "path/to/file",
      "line": 42,
      "source": "untrusted-input origin (entry point)",
      "sink_chain": ["entry function", "...", "sink function"],
      "description": "what was found",
      "validated": true,
      "false_positive": false,
      "reachable": true,
      "mitigated": false,
      "threat_scenario": "T1 | null",
      "remediation": "one-line fix hint"
    }
  ],
  "summary": {
    "total": 10,
    "critical": 1,
    "high": 3,
    "medium": 4,
    "low": 2,
    "false_positives": 0
  }
}

Step 7: Feed into Triage

After producing findings, suggest: "Run /triage to validate findings with adversarial review."

Anti-Rationalization

ExcuseRebuttalWhat to do instead
"It's an internal tool / not user-facing"Internal boundaries are attack surfaces — lateral movement starts inside.Trace internal entry points with the same rigor.
"The framework sanitizes automatically"Auto-escaping and parameterization have opt-outs, raw escapes, and edges; business logic is framework-agnostic.Find the raw/dangerous escapes and read them.
"We'll add validation later"Later never arrives for paths that already work.Finding = now; remediation line goes in the output.
"Grep found nothing, so we're clean"Grep misses aliased and framework-specific sinks by construction.A clean claim requires traced entry points, not an empty grep.
"Dependencies are vetted"Transitive dependencies aren't.Run the dependency pass every time the lockfile changes.

Evidence Required

  • Every finding cites file:line + the untrusted-input origin + the sink call chain
  • Each finding validated: reachable, not mitigated, severity confirmed
  • A clean (or low-finding) scan lists entry_points_traced — absence of evidence is not evidence of absence
  • All 4 dimensions completed (injection, auth, exposure, dependency)
  • VULN-FINDINGS.json written with summary
  • If THREAT_MODEL.md exists: each threat scenario mapped to findings
  • No CRITICAL/HIGH finding dismissed without explicit justification

Red Flags

  • Reporting grep hits without reading the surrounding code
  • Skipping framework-specific sink types (ORM raw escapes, non-auto-escaping templates)
  • Claiming clean without naming which entry points were traced
  • Scanning only changed files when full-surface scan was requested
  • Marking findings as false positives without a traced reason
  • VULN-FINDINGS.json with zero findings on a non-trivial codebase — the scan was likely incomplete, not the codebase clean
我的收藏