agentleFS
greglas75greglas75/zuvo/skills/pentest/SKILL.md

pentest

Skill7 starsChanged 27 days agoMITView on GitHub

Hybrid white-box + black-box penetration testing across 7 dimensions (PT1-PT7). Stack-aware source-to-sink tracing, exploit verification, CMS overlay, and deterministic finding aggregation. Uses explicit candidate schemas, canonical-key deduplication, score caps, and MUST-GATE enforcement. Flags: zuvo:pentest [path] | --url <url> | --from-audit <dir> | --cms <type> | --quick | --scope <path> | --dimensions PT1,PT2 | --verify-live | --rate <N> | --source | --runtime

  • Reads credentials

13 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

48 rules. 0 are common, 18 are distinctive, and 30 are very specific to this project.

  • 0

    Common

    Most of the 12 edited versions we read keep these.

  • 18

    Distinctive

    This author's own choices. Worth a look.

  • 30

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    When to use: Before releases, after zuvo:security-audit flags issues that need exploit verification, testing CMS installations, verifying …

    line 52
  2. Distinctive:

    Read in two stages. Stage 1 before Phase 0.

    line 87
  3. Distinctive:

    If any Stage 1 file is missing, STOP.

    line 115
  4. Very specific:

    Read ../../shared/includes/env-compat.md for agent dispatch, path resolution, and progress tracking.

    line 119
  5. Very specific:

    Use the deterministic preload helper FIRST: Run ~/.zuvo/compute-preload pentest "$PWD" before any ToolSearch.

    line 150
  6. Very specific:

    Read ../../shared/includes/codesift-setup.md for the full initialization sequence.

    line 152
  7. Very specific:

    Summary: Run the CodeSift setup from codesift-setup.md at skill start.

    line 154
  8. Distinctive:

    Before dispatching any Phase 1 agent:

    line 241
  9. Very specific:

    If CMS detected or --cms is set, enable the CMS overlay in Phase 5.

    line 265
  10. Very specific:

    Follow ../../shared/includes/scanner-invocation.md (exit-code trap: a scanner exits non-zero WHEN it finds vulns — that is success, key …

    line 279
  11. Distinctive:

    Check whether Playwright browser tooling is available in the current environment.

    line 322
  12. Distinctive:

    Create a shared context block for all source-analysis agents:

    line 326
  13. Distinctive:

    When dispatch fails, do exactly one of:

    line 361
  14. Very specific:

    Declare it incomplete: If you genuinely cannot cover the full scope this run, the Coverage Gate (2.4) FAILs by construction: set VERDICT = …

    line 364
  15. Distinctive:

    Every source-analysis agent must:

    line 370
  16. Very specific:

    Use the active stack profile from pentest-stack-profiles.md.

    line 372
  17. Very specific:

    Use the shared files_in_scope inventory from Phase 0.3.

    line 376
  18. Very specific:

    Never assign final IDs such as PT3-001.

    line 379
  19. Distinctive:

    If no source code is available, dispatch scanner agents instead.

    line 441
  20. Distinctive:

    If more than 50% of imported findings reference stale code (file missing or line content changed materially), warn the user and suggest …

    line 461
  21. Distinctive:

    Before any scoring or reporting:

    line 471
  22. Very specific:

    Check scope_ok on every candidate.

    line 473
  23. Very specific:

    If source_file or sink_file is outside the requested scope, force:

    line 474
  24. Distinctive:

    Do not count out-of-scope records in final findings.

    line 477
  25. Very specific:

    Do not deduplicate on file:line.

    line 483
  26. Distinctive:

    Update these logical artifacts after merge:

    line 496
  27. Very specific:

    Prefer the matching verify-live template from pentest-finding-registry.md, then refine it with overlay-specific ideas from …

    line 549
  28. Very specific:

    If safe_to_probe=false, do not improvise around the constraint.

    line 565
  29. Very specific:

    Skip if: No CMS detected in Phase 0.4 and no --cms flag.

    line 589
  30. Distinctive:

    ASSUME-EXPLOIT: Treat a trace as exploitable unless defense is proven sufficient.

    line 621
  31. Distinctive:

    PROOF: Every final finding needs a source trace or runtime evidence.

    line 623
  32. Very specific:

    All report paths in 7.3–7.4 are written under $PENTEST_DIR/ — i.e.

    line 674
  33. Distinctive:

    All final findings grouped by dimension

    line 683
  34. Very specific:

    every record in findings[], needs_review[], and exclusions[] has reason_code

    line 740
  35. Very specific:

    If validation fails, the run is invalid.

    line 742
  36. Very specific:

    If gate_status = FAIL OR coverage_gate = FAIL → VERDICT = INCOMPLETE, append [VALIDITY GATE FAIL] / [COVERAGE GATE FAIL] to NOTES.

    line 841
  37. Distinctive:

    Run: <ISO-8601-Z> pentest <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>

    line 849
  38. Very specific:

    Follow the retrospective protocol from retrospective.md.

    line 853
  39. Very specific:

    If gate check skips: print RETRO: skipped (trivial session) and proceed.

    line 855
  40. Very specific:

    After printing this block, append the Run: line value (without the Run: prefix) to the log file path resolved per run-logger.md.

    line 857
  41. Very specific:

    After logging the pentest run, automatically invoke zuvo:review on changed files.

    line 865
  42. Very specific:

    If there are uncommitted / staged changes: Skill(skill="zuvo:review")

    line 869
  43. Very specific:

    If pentest applied and committed fixes: Skill(skill="zuvo:review", args="HEAD~N")

    line 870
  44. Very specific:

    If pentest was audit-only (no code changes made): skip the chain, print No code changes -- review chain skipped.

    line 871
  45. Very specific:

    Use stack-aware recipes from pentest-stack-profiles.md, not hard-coded TS-only heuristics.

    line 888
  46. Very specific:

    Use framework overlays from pentest-stack-detection.md and pentest-stack-profiles.md when the repo matches them.

    line 889
  47. Distinctive:

    Use Sonnet-class models for source analyzers and scanners.

    line 890
  48. Distinctive:

    Use a stronger model for exploit verification on CRITICAL findings when available.

    line 891

“Common” is measured: kept by at least 6 in 10 of the 12 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

zuvo:pentest -- Hybrid Penetration Testing

White-box source-to-sink analysis combined with optional black-box runtime verification. zuvo:pentest is stack-aware: it selects sources, sinks, framework-safe patterns, and CodeSift recipes based on the detected project stack before dispatching any agents. Findings are built from a deterministic candidate schema, then merged with canonical-key deduplication and centralized scoring.

When to use: Before releases, after zuvo:security-audit flags issues that need exploit verification, testing CMS installations, verifying security fixes. Out of scope: Static code quality (use zuvo:code-audit), broad security posture review (use zuvo:security-audit), API contract audit (use zuvo:api-audit).

Argument Parsing

ArgumentEffect
[path]Limit analysis to a specific directory
--url <url>Target URL for runtime probes (enables hybrid mode)
--from-audit <dir>VERIFICATION mode -- verify PT/SEC findings from a prior audit directory
--cms joomla|wordpress|drupalForce-enable CMS overlay
--quickPT1 + PT3 + PT4 only (critical gates), no live probing
--scope <path>Alias for [path]
--dimensions PT1,PT2Audit only specified dimensions
--verify-liveEnable Phase 3 exploit verification against a running app
--rate <N>Requests per second cap (default: 5)
--sourceForce white-box track only -- skip live probes even if --url present
--runtimeForce black-box track only -- skip source analysis. Requires --url.
--strict-v2Count the 14 v2 vuln classes toward score caps / critical-gate totals — the 11 original (incl. both GraphQL classes) plus header_injection, token_compare_bypass, weak_credential_storage added 2026-08-01. Default: warning-only grace — identical spec to security-audit's "v2 class grace" section, and the graced set is all 14, not the original 11: a class added to v2 is a v2 class, and gracing 11 of 14 would silently enforce the three newest on day one. Warning-only through the 1.4.x window, full weight in 1.5.0; v2 findings are still reported, excluded only from gate totals (a CRITICAL or HIGH one is recorded through backlog-add like any other: 7.5); identical v2-grace: warning-only (N v2 findings reported, excluded from gate; --strict-v2 to enforce) line.

Mode Table

ModeTrackAgent TypeMax Concurrent
[path]White-boxSource analyzers3 global
--urlHybrid (white + black)Source + scanner3 global
--cms <type>CMS overlayCMS-aware analyzers3 global
--quickWhite-box (critical only)Source only1
--verify-liveVerificationExploit verifier1
--from-auditImport / verificationLead + verifier3 global
--sourceWhite-box onlySource analyzers3 global
--runtimeBlack-box onlyScanner agents3 global

Mandatory File Loading

Read in two stages. Stage 1 before Phase 0. Stage 2 before Phase 7.

Stage 1 -- Before starting (STOP if any missing):

CORE FILES LOADED:
  1. ../../rules/security.md                        -- READ/MISSING
  2. ../../shared/includes/env-compat.md           -- READ/MISSING
  3. ../../shared/includes/codesift-setup.md       -- READ/MISSING
  4. ../../shared/includes/pentest-output-contract.md -- READ/MISSING
  5. ../../shared/includes/pentest-stack-detection.md -- READ/MISSING
  6. ../../shared/includes/pentest-stack-profiles.md  -- READ/MISSING
  7. ../../shared/includes/pentest-safe-pattern-registry.md -- READ/MISSING
  8. ../../shared/includes/pentest-finding-registry.md -- READ/MISSING
  9. ../../shared/includes/pentest-source-sink-registry.md -- READ/MISSING
  10. ../../shared/includes/pentest-dedup-scoring.md   -- READ/MISSING
  11. ../../shared/includes/no-pause-protocol.md       -- READ/MISSING (HARD: no mid-loop pauses across dimensions/findings)

Stage 2 -- Before Phase 7 (report writing):

  12. ../../shared/includes/backlog-protocol.md     -- READ/MISSING
  13. ../../shared/includes/run-logger.md           -- READ/MISSING
  14. ../../shared/includes/retrospective.md        -- READ/MISSING
  15. ../../shared/includes/report-output-location.md -- READ/MISSING (resolves canonical $ZUVO_DIR — output MUST go to zuvo/audits/pentest/)

If any Stage 1 file is missing, STOP.

Environment Compatibility

Read ../../shared/includes/env-compat.md for agent dispatch, path resolution, and progress tracking.

MANDATORY TOOL CALLS — Pentest Validity Gate

This pentest is INVALID if any tool below is skipped when its trigger condition holds. "DEFERRED", "N/A", "scope too narrow" are NOT valid reasons.

ToolTriggerReasonSkip allowed?
scan_secretsAlwaysPT5 secret-leakage primitivesNO
search_patterns(injection sinks/eval/deserialize/command-injection)AlwaysPT2/PT3 sink discoveryNO
trace_call_chainEvery PT2/PT3 candidate sinkKEY — white-box source-to-sink tracingNO when sinks exist
find_referencesEvery cited sinkWho reaches this dangerous sinkNO when sinks exist
trace_routeBackend framework detectedEntry points (sources) for source-to-sink chainsNO when backend exists
audit_scanAlwaysCompound check covering several PT dimensionsNO
taint_traceDjango detectedPT2/PT3 Django taint propagationNO when Django
Stack-specific (nest_audit/php_security_scan/python_audit)Framework/language detectedStack-specific pentest gatesNO when matches

Forbidden escape hatches: same as security-audit (DEFERRED, N/A, codesift: unavailable, retrospective: skipped — all REJECTED).

Required POSTAMBLE: report on disk → retro appended → ~/.zuvo/append-runlog exit 0. Every HIGH/CRITICAL finding needs path/to/file.ext:LINE citation resolving in current tree (verify-audit gate).

Mandatory acknowledgment (REQUIRED — print verbatim before Phase 0)

Mandatory-tools-acknowledgment: I will run scan_secrets + search_patterns(injection sinks) + trace_call_chain (every cited sink) + find_references (every cited sink) + trace_route (when backend) + audit_scan + taint_trace (when Django) + stack-specific tools for this pentest. Every HIGH/CRITICAL finding will cite a `path/to/file.ext:LINE` resolving in the current tree.

CodeSift Integration

Use the deterministic preload helper FIRST. Run ~/.zuvo/compute-preload pentest "$PWD" before any ToolSearch. Copy the printed [CodeSift matching trace] verbatim and issue the printed ToolSearch(query="select:...") line. Math gate enforced.

Read ../../shared/includes/codesift-setup.md for the full initialization sequence.

Summary: Run the CodeSift setup from codesift-setup.md at skill start. Use the stack profile from pentest-stack-profiles.md to choose the right tools for the active stack. If CodeSift is unavailable, fall back to grep/find/manual tracing.

CodeSift Optimizations

TaskCodeSiftFallback
Scope inventoryget_file_tree or stack-specific route map toolsfind + rg --files
Sink discoverysearch_textGrep
Trace input to sinktrace_call_chain + find_referencesManual grep for call sites
Handler / view inventoryget_file_outline, search_symbols, trace_routeSequential Read + Grep
Guard / auth coveragesearch_symbols + find_referencesGrep for decorators, middleware, auth helpers

Degraded Mode (CodeSift unavailable)

CodeSift toolFallbackLost capability
trace_call_chainGrep for import referencesNo transitive call graph
find_referencesGrep for callersWeaker constant propagation
search_symbolsGrep for function / class namesLess precise matching
get_file_outlineRead full fileMore tokens consumed
trace_routeManual route tracingNo route-to-handler map

Safety Gates (NON-NEGOTIABLE)

Gate 1 -- Authorization

Prompt the user for explicit authorization before any probing. Block on "no".

PENETRATION TESTING requires authorization.
Target: {URL or CWD}
Do you have authorization to test this system? [y/n]

Gate 2 -- No Production

Refuse if URL lacks localhost/127.0.0.1/staging/dev. Override only with --i-know-what-im-doing.

Gate 3 -- Read-Only Default

GET requests proceed freely. POST/PUT/DELETE require per-endpoint approval. Exception: POST /auth/login.

Gate 4 -- Rate Cap

Max --rate req/s (default 5). Stop after 3 consecutive 5xx.

Gate 5 -- PII Scrub

Mask tokens, passwords, cookies, email addresses, and API keys in all output.

Gate 6 -- Database Safety

White-box: zero DB queries. Black-box: time-based detection only. Verify-live: read-only payloads.


Phase 0: Detect Mode, Stack, and Scope

0.1 Detect Mode

Source code in CWD?           -> WHITE-BOX track (Phase 1 source analysis)
--url without source?         -> BLACK-BOX track (Phase 1 scanner)
--from-audit?                 -> VERIFICATION mode (skip to Phase 3)

0.2 Detect Stack and Load Stack Rules

Follow pentest-stack-detection.md.

Required actions:

  1. Detect the primary backend stack.
  2. Detect frontend and site-framework overlays, including Next App Router, Next Pages Router, and React SPA where applicable.
  3. Detect headless CMS overlays.
  4. Detect CMS overlays, including WordPress / Joomla / Drupal.
  5. Detect framework overlays from pentest-stack-detection.md.
  6. Load the matching files from ../../rules/.
  7. Load safe-pattern IDs from pentest-safe-pattern-registry.md.
  8. Load finding-type to CWE / probe mappings from pentest-finding-registry.md.
  9. Load source/sink discovery regexes from pentest-source-sink-registry.md.
  10. Select the active stack profile and any active overlays from pentest-stack-profiles.md.

0.3 Build Authoritative Scope Inventory

Before dispatching any Phase 1 agent:

  1. Resolve the target scope from [path] or --scope.
  2. Build the complete file inventory for that scope.
  3. Record one authoritative files_in_scope count.
  4. Pass that exact count to every agent.

Print:

SCOPE INVENTORY
  Scope: src/example/
  Files in scope: 92
  Stack profile: python-django

0.4 CMS Detection

[ -f configuration.php ] && echo "CMS: Joomla"
[ -f wp-config.php ] && echo "CMS: WordPress"
[ -f sites/default/settings.php ] && echo "CMS: Drupal"

If CMS detected or --cms is set, enable the CMS overlay in Phase 5.

0.5 Tool Availability

which curl 2>/dev/null && echo "curl: OK" || echo "curl: MISSING"
which semgrep 2>/dev/null && echo "semgrep: available" || echo "semgrep: fallback to grep"
which testssl 2>/dev/null && echo "testssl: available" || echo "testssl: fallback to openssl"
which gitleaks 2>/dev/null && echo "gitleaks: available" || echo "gitleaks: skip secret scan"
which npm pip-audit osv-scanner 2>/dev/null | head -1 || true   # SCA tool presence

0.5b Dependency Vulnerability Scan (SCA)

Follow ../../shared/includes/scanner-invocation.md (exit-code trap: a scanner exits non-zero WHEN it finds vulns — that is success, key DEGRADED on output not exit code).

Pentest run alone (no security-audit / dependency-audit in the same chain) must still cover known-vulnerable dependencies. Run SCA only when a resolved dependency tree exists — a lockfile/manifest preflight prevents a false "0 vulns" on an un-built tree:

# Preflight: SCA needs a RESOLVED, PINNED dependency tree. osv-scanner is the
# universal auditor (npm/pnpm/yarn/poetry/Pipfile/Cargo/Go/...) — prefer it; fall
# back to the ecosystem tool that actually consumes the lockfile present.
# CRITICAL: npm/pnpm/pip-audit/osv-scanner EXIT NON-ZERO WHEN VULNS ARE FOUND — a
# non-zero exit is a SUCCESSFUL scan, not a failure. So key the degraded decision on
# whether the tool produced usable JSON output, NOT on its exit code.
run_sca() { OUT="$("$@" 2>/dev/null)"; case "$OUT" in ''|null) return 1 ;; *) printf '%s\n' "$OUT"; return 0 ;; esac; }
# Audit EVERY lockfile present (polyglot/monorepo) — not just the first.
audit_lock() { # $1 = lockfile path
  local LOCK="$1"
  if command -v osv-scanner >/dev/null 2>&1; then
    run_sca osv-scanner --format json --lockfile "$LOCK" || echo "DEGRADED: osv-scanner produced no output for $LOCK"
  else case "$(basename "$LOCK")" in
    package-lock.json) run_sca npm audit --json || echo "DEGRADED: npm audit needs node_modules (npm ci) for $LOCK" ;;
    pnpm-lock.yaml)    run_sca pnpm audit --json || echo "DEGRADED: pnpm audit needs install for $LOCK" ;;
    yarn.lock)         echo "DEGRADED: no yarn auditor without osv-scanner ($LOCK)" ;;
    poetry.lock|Pipfile.lock) echo "DEGRADED: pip-audit audits the env not $LOCK — install osv-scanner or run in venv" ;;
    requirements.txt)  run_sca pip-audit -r "$LOCK" -f json && echo "NOTE: $LOCK may be unpinned" || echo "DEGRADED: pip-audit -r failed for $LOCK" ;;
    *) echo "DEGRADED: $LOCK present but no auditor without osv-scanner — install osv-scanner (IC-5 reduced, NOT an IC-2 failure)" ;;
  esac; fi
}
FOUND=$(find . -maxdepth 3 \( -name package-lock.json -o -name pnpm-lock.yaml -o -name yarn.lock \
  -o -name poetry.lock -o -name Pipfile.lock -o -name requirements.txt -o -name Cargo.lock -o -name go.sum \) \
  -not -path '*/node_modules/*' 2>/dev/null)
if [ -z "$FOUND" ]; then
  echo "DEGRADED: SCA skipped (no lockfile); IC-5 class_coverage reduced, NOT an IC-2 surface-gate failure"
else
  printf '%s\n' "$FOUND" | while read -r L; do [ -n "$L" ] && { echo "== SCA: $L =="; audit_lock "$L"; }; done
fi

SCA findings map to finding_type per dependency CVE. Absent SCA lowers IC-5 class_coverage and emits the DEGRADED line — it never fails the IC-2 surface gate (a project with no lockfile is not an un-enumerated attack surface). Never emit a "0 dependency vulns" result from an un-built tree.

Missing tools never block the audit -- they reduce coverage. Document this in the report. Check whether Playwright browser tooling is available in the current environment.

0.6 Build Phase 1 Context

Create a shared context block for all source-analysis agents:

PHASE 1 CONTEXT
  Stack profile: {active profile}
  Framework overlays: {overlay list}
  Files in scope: {N}
  Scope path: {path}
  Active dimensions: {PT list}
  Safe-pattern IDs: {relevant pattern_id list}
  Source families: {relevant source regex families}
  Sink families: {relevant sink regex families}

Materialize these logical artifacts for later phases:

  • phase1-context
  • phase1-findings
  • phase1-exclusions
  • probe-plan

PT6 and PT7 must consume phase1-context, phase1-findings, and phase1-exclusions.


Phase 1: Source Analysis (WHITE-BOX) or Scanning (BLACK-BOX)

WHITE-BOX Track -- Source Analyzer Agents

Spawn via the task/agent dispatch model from env-compat.md. Max 3 agents in parallel globally.

Dispatch-Failure Fallback (breadth is non-negotiable)

If agent dispatch is throttled, errors out, or returns partial output (API rate-limit, token exhaustion, a workflow that dies with 0 usable output, etc.), you MUST NOT silently fall back to "inline, go deep on a few high-signal sinks and call it done." That converts a breadth tool into a spot-check and produces a falsely-complete report.

When dispatch fails, do exactly one of:

  1. Complete breadth inline. Re-run the remaining dimensions yourself sequentially (reduce parallelism, add backoff/retries), iterating until the union of files_checked reaches every file in files_in_scope across all in-scope dimensions. Slower is fine; partial is not.
  2. Declare it incomplete. If you genuinely cannot cover the full scope this run, the Coverage Gate (2.4) FAILs by construction: set VERDICT = INCOMPLETE, label the run DEGRADED, and list every uncovered file. Do not emit a passing/partial grade.

A coverage shortfall caused by infrastructure failure is still a coverage shortfall. "The 7-agent workflow rate-limited so I checked the obvious sinks inline" is a Coverage Gate FAIL, never a PASS with a footnote.

Global Rules

Every source-analysis agent must:

  1. Use the active stack profile from pentest-stack-profiles.md.
  2. Seed discovery from pentest-source-sink-registry.md before using broader semantic search.
  3. Match framework defenses by canonical pattern_id from pentest-safe-pattern-registry.md.
  4. Resolve cwe and probe_template_id from pentest-finding-registry.md when the type exists there.
  5. Use the shared files_in_scope inventory from Phase 0.3.
  6. Emit candidate records using pentest-output-contract.md.
  7. Classify each record as confirmed, excluded, or needs_review.
  8. Never assign final IDs such as PT3-001.

Batch 1 (parallel) -- independent sink tracing:

  • PT1 (Injection: SQL, NoSQL, command, SSTI): Trace every attacker-controlled input to query/exec/eval sink using the active stack profile.
  • PT2 (XSS and Output Encoding): Trace attacker-controlled input to HTML/template/output sinks using the active stack profile.
  • PT5 (SSRF and External Interactions): Trace attacker-controlled URLs or hosts to outbound HTTP/file retrieval sinks. If the stack has no outbound client primitives, emit a coverage note as status=excluded, not a finding.

Batch 2 (parallel) -- cross-endpoint reasoning:

  • PT3 (Auth and Session): Session fixation, cookie flags, OAuth / OIDC state handling, password / 2FA flows, token binding, replay windows.
  • PT4 (Authorization and Access Control): Guard coverage on every mutation endpoint. Guard + query filter defense in depth. IDOR / org / tenant scoping.
    • Next.js 'use server' sub-check (MANDATORY when Next.js detected): enumerate EVERY module containing a 'use server' directive (grep -rl "['\"]use server['\"]" --include=*.ts --include=*.tsx) and, for each exported server action, assert a first-line auth/authz guard (an auth()/getCurrentUser()/requireRole()/tenant-scope check before any data access or mutation). A 'use server' export with no guard before its first DB/mutation call is a CRITICAL IDOR/broken-access finding — server actions are public POST endpoints by default, callable by anyone who can reach the route. List the un-guarded actions by file:line as evidence; "looks internal" is not a guard.

Batch 3 (parallel, after Batch 1 + Batch 2 merge):

  • PT6 (Input Vectors): File uploads, CSRF, header injection, redirect edge cases, parser abuse. Must consume the merged Phase 1 context and exclusions.
  • PT7 (Business Logic): TOCTOU, race conditions, state-machine bypass, price-from-client, replay windows. Must consume the merged Phase 1 context and exclusions.

Candidate Record Example

Agents emit candidate records, not final findings:

{
  "schema_version": "4.0",
  "dimension": "PT3",
  "type": "oauth_state_missing",
  "title": "OAuth implicit flow does not require state parameter",
  "severity": "HIGH",
  "status": "confirmed",
  "reason_code": "proof_complete",
  "source_agent": "PT3-auth-session",
  "source_file": "src/app/oauth.py:101",
  "sink_file": "src/app/oauth.py:528",
  "function_symbol": "OAuthAuthorizeView.approve",
  "endpoint": "GET /oauth/authorize/",
  "trace": ["step1", "step2"],
  "scope_ok": true,
  "files_checked": 92,
  "files_in_scope": 92,
  "defense_present": false,
  "defense_sufficient": false,
  "confidence": 0.85,
  "confidence_rationale": "Complete code trace with no effective defense and a clear exploit path.",
  "verification_status": "trace_confirmed",
  "safe_pattern_matched": [],
  "canonical_key": "PT3|OAuthAuthorizeView.approve|oauth_state_missing|GET /oauth/authorize/",
  "probe_template_id": "probe-oauth-state",
  "suggested_probe": "GET /oauth/authorize/?response_type=token&state=",
  "runtime_preconditions": ["pre-approved app exists"],
  "safe_to_probe": true,
  "cwe": "CWE-352"
}

BLACK-BOX Track -- Scanner Agents

Dispatch follows ../../shared/includes/execution-policy.md through env-compat. Reuse existing authorization within that policy; session restrictions take precedence. Run each required gate and report its actual independence or an unmet requirement.

If no source code is available, dispatch scanner agents instead. One per dimension, max 3 in parallel globally. Each follows a 4-phase cycle:

  1. Recon
  2. Experiment
  3. Test
  4. Verify

Scanner agents must still emit candidate records using pentest-output-contract.md. Findings below confidence 0.7 are not promoted to final findings.

VERIFICATION Mode

Skip Phase 1 entirely. Parse PT/SEC findings from --from-audit and normalize them into candidate records. When the imported findings come from security-audit, apply Cross-Skill Reconciliation (IC-3) from pentest-dedup-scoring.md: merge a pentest and a security-audit record for the same {cwe, file, sink_symbol, source_or_entry} (all four present and equal — fail-closed) into one finding, resolving verification-status and severity on their two separate axes per IC-3, while keeping distinct-source records separate.

Rules:

  • Preserve original IDs in audit trail only.
  • Recompute canonical_key.
  • Set verification_status="needs_runtime_check" unless runtime evidence already exists.
  • Preserve or assign reason_code.
  • If more than 50% of imported findings reference stale code (file missing or line content changed materially), warn the user and suggest re-running the underlying audit.

Phase 2: Merge, Scope Enforcement, Dedup, Coverage Sanity

Collect all Phase 1 candidate records into a single queue.

2.1 Scope Enforcement

Before any scoring or reporting:

  1. Check scope_ok on every candidate.
  2. If source_file or sink_file is outside the requested scope, force:
    • status="excluded"
    • exclusion reason out_of_scope
  3. Do not count out-of-scope records in final findings.

2.2 Deduplication

Follow pentest-dedup-scoring.md.

Do not deduplicate on file:line. Use canonical_key and merge alias evidence into one survivor.

2.3 Audit Trail and Context Artifacts

Preserve an audit_trail[] collection for:

  • source agent names
  • duplicate merges
  • status changes
  • reason_code
  • matched safe patterns
  • resolved probe_template_id

Update these logical artifacts after merge:

  • phase1-findings
  • phase1-exclusions
  • probe-plan

2.4 Coverage Gate (HARD — blocks the verdict, not a report footnote)

Follow pentest-dedup-scoring.md.

Take the union of every dimension's files_checked and compare it against the authoritative files_in_scope inventory from Phase 0.3. Compute coverage_pct = |union(files_checked)| / files_in_scope.

This is a verdict-blocking gate, not an advisory note. Going deep on a handful of high-signal sinks while leaving most of the attack surface un-enumerated is the failure this gate exists to catch:

  • Every in-scope file NOT covered by any dimension must be explicitly listed with a documented reason code (out_of_scope, no_sink_primitives, static_asset, generated, etc.). A reason of "low signal" or "looked unlikely" is not acceptable — every route handler / entry point / sink-bearing module must actually be traced or justified.
  • coverage_gate = PASS only when coverage_pct ≥ 0.90 AND every remaining uncovered file has a documented reason code.
  • Otherwise coverage_gate = FAIL. A FAIL means the run did not cover its declared scope: set VERDICT = INCOMPLETE, cap grade at FAIL, and list the uncovered files in the report. You may not print a passing/partial grade over an un-enumerated attack surface.

Record coverage_pct, files_checked_union, uncovered_files[], and coverage_gate in findings.json.summary.

2.5 Queue Summary

Print a merge summary with separate counts:

MERGED QUEUE
  Confirmed: 9
  Needs Review: 2
  Exclusions: 6
  Duplicates collapsed: 3
  Low-signal dimensions: PT1

Phase 3: Exploit Verification (CONDITIONAL -- requires --verify-live)

Skip if: --verify-live is not set. Findings remain trace_confirmed or needs_runtime_check.

Process findings sequentially (one at a time -- Gate 3 read-only).

For each confirmed finding:

  1. Feasibility check: endpoint reachable? auth required? environment safe? safe_to_probe=true?
  2. Resolve probe_template_id from pentest-finding-registry.md. 2b. probe_template_id == "static-only" → DO NOT PROBE. STOP HERE for this finding. Leave verification_status: trace_confirmed, record probe: skipped (static-only by registry), and move on. The registry marks a type static-only precisely because a runtime proof would BE the attack: redos's "probe" is driving a catastrophic-backtracking regex against a live target — i.e. deliberately causing the denial of service Gate 4 (rate cap) and Gate 6 (read-only, reversible payloads) exist to prevent. There is no ### static-only template and there must never be one; "craft a probe from the mapped template" does NOT apply to this branch.
  3. Reuse suggested_probe if present; otherwise craft a minimal read-only probe from the mapped template.
  4. Prefer the matching verify-live template from pentest-finding-registry.md, then refine it with overlay-specific ideas from pentest-stack-profiles.md.
  5. Execute via curl or Playwright.
  6. Capture evidence.

Update verification_status:

  • runtime_verified
  • blocked_by_defense
  • needs_runtime_check

Update confidence:

  • runtime_verified -> 0.95
  • blocked_by_defense -> 0.6 and move to excluded
  • failed reproduction without proof of defense -> remain needs_runtime_check

If safe_to_probe=false, do not improvise around the constraint. Record the unmet runtime_preconditions and keep the finding in needs_runtime_check.


Phase 4: Runtime Checks (PT8)

Skip if: zuvo:security-audit --live-url already ran and its live results still match the current code / environment.

Dispatch a lightweight agent to check:

  • security headers
  • CORS
  • TLS version
  • error disclosure
  • cookie flags

Rate limit per --rate.

PT8 contributes narrative runtime coverage, not a new scoring dimension unless explicitly requested by the user.


Phase 5: CMS Overlay (CONDITIONAL)

Skip if: No CMS detected in Phase 0.4 and no --cms flag.

Apply the CMS overlay from pentest-stack-profiles.md:

  1. Version -> CVE lookup
  2. Extension / plugin inventory + known vulnerability check
  3. Admin panel exposure
  4. Config review (debug mode, error reporting, DB credentials)
  5. File permissions
  6. Custom extension code review (trace input to sink)

Phase 6: Confidence Filter and MUST-GATEs

Apply three filter layers to the merged queue:

Layer 1 -- Confidence Threshold

status=confirmed with confidence < 0.7 is demoted to needs_review.

Layer 2 -- Status Correction

Apply pentest-output-contract.md hard rules centrally:

  • defense_sufficient=true -> excluded
  • scope_ok=false -> excluded
  • hedged language without proof -> needs_review
  • preserve reason_code on every disposition change

Layer 3 -- MUST-GATEs

  • ASSUME-EXPLOIT: Treat a trace as exploitable unless defense is proven sufficient.
  • NO-HEDGING: "Might be vulnerable" -> prove it or move it out of final findings.
  • PROOF: Every final finding needs a source trace or runtime evidence.
  • FULL-COVERAGE: Agent must report files_checked against the full scope inventory.
  • STRICT-SEQUENCE: Agent must follow sink -> trace -> defense -> verdict.

Print filter summary:

CONFIDENCE FILTER
  Confirmed input: 13
  Demoted to Needs Review: 2
  Excluded by rules: 4
  Final findings: 9

Phase 7: Report Generation

7.1 Scoring

Follow pentest-dedup-scoring.md.

Requirements:

  • compute raw_score first
  • compute final_score after score caps
  • emit cap_reason
  • emit grade
  • emit verdict

Per-dimension score remains 0-10:

  • 0 = critical gate fail (undefended sink confirmed)
  • 5 = defenses present but gaps found
  • 8 = minor issues only
  • 10 = all checks pass

Critical gates: PT1=0 OR PT3<3 OR PT4<3 -> grade FAIL.

Coverage gate (from 2.4): coverage_gate = FAIL -> VERDICT = INCOMPLETE, grade capped at FAIL. A pentest that did not enumerate its declared attack surface cannot earn a passing or partial grade, regardless of per-dimension scores. The grade reflects what was actually checked, not the subset that happened to be high-signal.

7.2 Output Directory

Resolve the canonical zuvo output root (see ../../shared/includes/report-output-location.md), then create the pentest bundle under audits/. Never write to a bare pentest-results/ at the scope path — that scatters output outside the visible project-root zuvo/ folder.

ZUVO_DIR="${ZUVO_OUTPUT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)/zuvo}"
PENTEST_DIR="$ZUVO_DIR/audits/pentest"
mkdir -p "$PENTEST_DIR/evidence"

All report paths in 7.3–7.4 are written under $PENTEST_DIR/ — i.e. <project-root>/zuvo/audits/pentest/. The --from-audit <dir> flag consumes this same directory.

7.3 Write Reports

$PENTEST_DIR/pentest-report.md (zuvo/audits/pentest/pentest-report.md) -- Full report:

  1. Metadata (target, date, mode, scope, stack profile, tools used, gates passed)
  2. Executive summary (grade, verdict, raw score, final score, cap reason, finding counts)
  3. Critical findings
  4. All final findings grouped by dimension
  5. Needs Review
  6. Exclusions applied
  7. Audit trail summary
  8. Coverage sanity results
  9. Variant analysis results
  10. Remediation roadmap
  11. Manual checklist

$PENTEST_DIR/findings.json (zuvo/audits/pentest/findings.json) -- Machine-readable:

{
  "schema_version": "4.0",
  "metadata": {
    "target": "/repo",
    "scope": "src/app/",
    "stack_profile": "python-django",
    "files_in_scope": 92
  },
  "summary": {
    "total_findings": 9,
    "by_severity": { "critical": 0, "high": 4, "medium": 5 },
    "by_dimension": { "PT3": 3, "PT4": 2, "PT7": 4 },
    "raw_score": 73,
    "final_score": 60,
    "cap_reason": "HIGH findings present",
    "grade": "C",
    "verdict": "WARN",
    "critical_gates": "PASS",
    "coverage_pct": 0.97,
    "files_checked_union": 89,
    "uncovered_files": [{ "path": "public/vendor.js", "reason_code": "static_asset" }],
    "coverage_gate": "PASS"
  },
  "findings": [{ "id": "PT3-001", "reason_code": "proof_complete", "probe_template_id": "probe-oauth-state", "merged_from": ["PT3-auth-session"] }],
  "needs_review": [{ "dimension": "PT6", "reason_code": "runtime_required" }],
  "exclusions": [{ "reason_code": "out_of_scope" }],
  "audit_trail": [{ "canonical_key": "PT3|...", "source_agent": "PT3-auth-session" }]
}

summary.* must be generated from the final findings[] collection only. If the counts do not match, the run is invalid and must be corrected before output.

7.4 Self-Check

Validate the machine-readable output before printing the completion block:

./scripts/validate-pentest-output.sh "$PENTEST_DIR/findings.json"

Required checks:

  • summary.total_findings == len(findings[])
  • summary.by_severity matches final findings[]
  • summary.by_dimension matches final findings[]
  • every record in findings[], needs_review[], and exclusions[] has reason_code

If validation fails, the run is invalid. Correct the output before printing PENTEST COMPLETE.

$PENTEST_DIR/executive-summary.md (zuvo/audits/pentest/executive-summary.md) -- 1-page stakeholder overview.

$PENTEST_DIR/evidence/PT3-001/ (zuvo/audits/pentest/evidence/…) -- Per-finding evidence:

  • trace.md
  • poc.sh (if --verify-live)
  • screenshot.png (if Playwright)

Finding Format

### PT{D}-{NNN}: {Title}
Dimension: PT{D} -- {name}
Severity: CRITICAL / HIGH / MEDIUM
Confidence: {0.0-1.0}
Verification Status: trace_confirmed / runtime_verified / needs_runtime_check
Probe Template ID: {probe_template_id}
CWE: CWE-{NNN}
Reason Code: {reason_code}
Source: {file}:{line}
Sink: {file}:{line}
Trace: source -> [intermediate steps] -> sink
Defense Present: {yes/no}
Defense Sufficient: {yes/no}
Canonical Key: {canonical_key}
Merged From: {source_agent list}
Evidence: {code trace or runtime capture, scrubbed, max 20 lines}
Impact: {what an attacker achieves}
Fix: {remediation with code}

7.5 Backlog Integration

Every CRITICAL / HIGH final finding goes through backlog-add, one call per finding (../../shared/includes/backlog-protocol.md "Filing from a skill"; the bars and the dedupe are the protocol's, so compute no fingerprint and restate nothing). The class is SEC; the dimension lives in the title's PT<n>: prefix, because backlog-add has no --dimension argument. <level> is critical or high.

A black-box finding on a live target: ~/.zuvo/backlog-add --repo "$R" --class SEC --conf <N> --source pentest --target "$U" --evidence "$E" --severity <level> --title "PT<n>: $T", with U the URL or host and E the request and response that show it; it takes no --anchor.

A white-box finding traced to a file and line: ~/.zuvo/backlog-add --repo "$R" --class SEC --conf <N> --source pentest --file "$F" --line <n> --anchor "$A" --severity <level> --title "PT<n>: $T", with A copied from the line that shows the defect or, for something absent (no auth check), from the line of the code that lacks it ("The location rule").

A white-box finding that Phase 3 confirmed with --verify-live is filed ONCE, with the white-box call above, to which you append --evidence "$E" (the live request and response, redacted below; optional on a file record). It gets no separate black-box record, because a record names a file or a target, never both.

Redact E before you assign it, in both forms. The findings ledger is a committed git file, and E may hold raw traffic. Replace the value of every Authorization, Proxy-Authorization, Cookie, Set-Cookie and X-Api-Key-style header (any header that carries a credential) with <redacted>, keeping the header name, and replace every bearer or session token, API key and password in a body or query string with <redacted>. Gate 5 (PII Scrub) applies to what is recorded as it does to the report.

A secret is never copied into any field (backlog-protocol.md "Filing from a skill", "The location rule", the secrets sentence), A, T and U included, not only E. For a hardcoded-secret finding the anchor A is the key or variable name on the line (API_KEY=), never its value; U has no query string.

Assign "$R" and "$T" before each call, "$U" and "$E" only for a black-box finding, "$F" and "$A" only for a white-box one ("$E" too for a verify-live-confirmed one), as "Free text goes through a variable" in that section says; an empty value you pass is a bug, and backlog-add refuses a missing required field (REFUSED missing-field:<f>). Report the B- or F-id each call prints and Recorded: N (backlog: a, ledger: b) ("The report line" in backlog-protocol.md), plus each NOT PERSISTED (<reason>) by name; on a non-zero exit act on the protocol's exit table, and never append by hand.

This audit does not commit: it stages nothing. It lists the wrote= paths backlog-add printed as Backlog writes: <path>, ... (Backlog writes: none when there are none), in its report for the user when it runs standalone, and in its report for the calling skill's closing commit to carry when another skill runs it (backlog-protocol.md "Filing from a skill", Committing what backlog-add wrote).

7.6 Next-Action Routing

ConditionSuggested Action
CRITICAL/HIGH findingszuvo:refactor to fix vulnerable code paths
Auth gaps foundzuvo:security-audit full for broader posture check
Out-of-scope issues surfacedSuggest a focused follow-up audit on the owning module
Runtime preconditions not metEmit a follow-up --verify-live probe plan instead of closing the item
CMS issuesUpdate CMS + extensions, re-run zuvo:pentest --cms
All clearAuto-chain to zuvo:review (see below)

PENTEST COMPLETE

Grade: [A/B/C/D/FAIL] | Verdict: [PASS/WARN/FAIL] Raw Score: [N] / 100 | Final Score: [N] / 100 | Cap: [reason/none] Mode: [white-box / hybrid / black-box / verification] Dimensions: [N scored] | Critical gates: [PASS/FAIL] Findings: [N critical] / [N high] / [N medium] / [N total]

Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)

VALIDITY GATE
  triggers_held: language=<X> framework=<X> backend=<yes|no> django=<yes|no>
  required_tool_calls:
    scan_secrets: [<N> hits | NOT_CALLED — VIOLATES_TRIGGER]
    search_patterns(sinks): [<N> hits | NOT_CALLED — VIOLATES_TRIGGER]
    trace_call_chain: [<N> sink chains traced | NOT_CALLED — VIOLATES_TRIGGER]
    find_references: [<N> ref-checks | NOT_CALLED — VIOLATES_TRIGGER]
    trace_route: [<N> entry points | not_required (no backend) | NOT_CALLED — VIOLATES_TRIGGER]
    audit_scan: [<N> compound findings | NOT_CALLED — VIOLATES_TRIGGER]
    taint_trace: [<N> taints | not_required (no Django) | NOT_CALLED — VIOLATES_TRIGGER]
    stack_specific: [<result> | not_required | NOT_CALLED — VIOLATES_TRIGGER]
  coverage:
    files_in_scope: <N>
    files_checked_union: <M>
    coverage_pct: <X%>
    uncovered_undocumented: [<count> — list files | 0]
    coverage_gate: [PASS | FAIL — <pct below 0.90 / N files uncovered with no reason code>]
  postamble:
    retros_log_appended: [yes(bytes_added=N) | NOT_APPENDED — VIOLATES_REQUIRED_POSTAMBLE]
    retros_md_appended: [yes(entry_count=N) | NOT_APPENDED — VIOLATES_REQUIRED_POSTAMBLE]
    verify_audit_pass: [yes(<verified>/<total>) | NOT_RUN | REJECTED]
  gate_status: [PASS | FAIL — <which gates missing>]

If gate_status = FAIL OR coverage_gate = FAIL → VERDICT = INCOMPLETE, append [VALIDITY GATE FAIL] / [COVERAGE GATE FAIL] to NOTES.

Append the Run line via the retro-gated wrapper (NOT direct >> runs.log):

printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog

Run: <ISO-8601-Z> pentest <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>

Retrospective (REQUIRED)

Follow the retrospective protocol from retrospective.md. Gate check -> structured questions -> TSV emit -> markdown append. If gate check skips: print RETRO: skipped (trivial session) and proceed.

After printing this block, append the Run: line value (without the Run: prefix) to the log file path resolved per run-logger.md.

Verdict rules come from pentest-dedup-scoring.md.


Post-Pentest: Automatic Review Chain (MANDATORY)

After logging the pentest run, automatically invoke zuvo:review on changed files. Do not ask the user.

Scope selection:

  • If there are uncommitted / staged changes: Skill(skill="zuvo:review")
  • If pentest applied and committed fixes: Skill(skill="zuvo:review", args="HEAD~N")
  • If pentest was audit-only (no code changes made): skip the chain, print No code changes -- review chain skipped.
# Check for uncommitted changes first
git diff --stat HEAD
# If output is empty, check for recent commits from this session
git log --oneline -5
# Then invoke with appropriate scope
Skill(skill="zuvo:review")

This ensures adversarial code review runs before push on all supported platforms.


Execution Notes

  • Use stack-aware recipes from pentest-stack-profiles.md, not hard-coded TS-only heuristics.
  • Use framework overlays from pentest-stack-detection.md and pentest-stack-profiles.md when the repo matches them.
  • Use Sonnet-class models for source analyzers and scanners.
  • Use a stronger model for exploit verification on CRITICAL findings when available.
  • Max 3 parallel agents globally.
  • White-box agents are read-only.
  • Black-box agents require Gate 1 authorization.
  • Estimated durations: --quick ~5 min, white-box ~15 min, hybrid ~25 min, full + --verify-live ~40 min.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.