Hybrid FP-reduction — joern when present, LLM fallback when absent. Six-stage rubric (Stage 0 + Stages 1-5) applied to every finding; emits the disposition register.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Hybrid FP-reduction — joern when present, LLM fallback when absent. Six-stage rubric (Stage 0 + Stages 1-5) applied to every finding; emits the disposition register.
role
worker
user-invocable
false
version
1.0.0
maintainers
["bdfinst","unassigned"]
required-primitives-contract
^1.0.0
False-Positive Reduction (hybrid joern + LLM)
Purpose
Transform a stream of unified findings into a disposition register that the exec-report-generator can trust. Every finding gets a verdict (true_positive | likely_true_positive | uncertain | likely_false_positive | false_positive), a reachability trace, an exploitability score, and a reachability_source tag (joern-cpg or ).
llm-fallback
The skill's job is to remove noise without suppressing real issues. False positives waste analyst attention; missed true positives get someone fired.
Six-stage rubric (applied in order; each stage can downgrade severity or change verdict)
Lifted from the opus_repo_scan_test reference's § analyze-11 framework with extensions for the disposition-register output format. Stage 0 is new: a self-adversarial pre-pass that sharpens Stage 1 and strengthens the audit trail.
Stage 0 — Devil's advocate
Question: What is the strongest argument that this finding is NOT a vulnerability?
The agent generates a counter-argument before applying the rubric. This is not a skip gate — all five subsequent stages still run. The purpose is twofold:
Sharpen Stage 1: a strong counter-argument gives Stage 1 a concrete hypothesis to test (is the path actually dead / test-only?) rather than an open-ended search.
Strengthen the audit trail: a true_positive that explicitly refuted a counter-argument is more trustworthy than one that never examined the counter-case. A well-reasoned false_positive is more trustworthy than a silent discard.
Counter-argument prompts:
Framework/runtime protection: does the tech stack have a built-in prevention for this class (ORM parameterization, template auto-escaping, TLS termination at the LB)?
Trusted caller: is this code only reachable from internal, trusted, or admin-only paths?
Non-production context: is the file a migration, test fixture, seed script, or utility that RECON's entry_points don't include?
Rule pattern noise: does this rule commonly fire on intentional non-exploitable configurations?
Disposition rules:
Strong counter-argument → da_strong: true; Stage 1 tests the hypothesis
Question: Is this code executed in production at all?
Disposition rules:
Dead code (no inbound call graph from any entry point) → verdict: false_positive, severity → info presentational.
Test-only paths (only reached from test code) → verdict: likely_false_positive, severity → one level down (CRITICAL → HIGH, HIGH → MEDIUM).
Feature-flagged-off in all production configs → one level down, verdict stays true_positive.
Reached from production entry point → no change; record the entry point in reachability.rationale.
Joern-present mode: reachability is computed from the CPG by tracing back from the finding location to HTTP/CLI/lambda/cron entry points.
Joern-absent mode: the agent reasons from RECON's entry_points and security_surface fields, plus grep over the call sites. Tag each entry with reachability_source: llm-fallback.
Stage 2 — Environment context
Question: Could deployed configuration override the committed value, making the finding inert?
Disposition rules:
Confirmed override at deploy time (e.g. env var in values.yaml or Helm chart overrides a committed default) → one level down, verdict: likely_true_positive (the committed value is still a weak default).
No override found → full severity, verdict unchanged.
The agent consults docker-compose*.yml, values.yaml, helmfile.yaml, k8s/*.yaml, and any CI-scoped env vars discoverable in .github/workflows/* or GitLab equivalents.
Stage 3 — Compensating controls
Question: Is there a control in the repo that mitigates this finding's impact?
Disposition rules:
Confirmed in-repo control (WAF rule, rate limiter, input validation layer upstream of the finding, idempotency key check, etc.) → one level down, verdict likely_true_positive with the control's file:line in the rationale.
Assumed-only ("we have a WAF in prod" — not verifiable from the repo) → no change.
Absent → no change.
Stage 4 — Deduplication
Question: Is this the same root cause as another finding already in the register?
Disposition rules:
Same rule_id + same value (e.g. same secret SHA-256) across multiple files → collapse to ONE finding with a locations array; emit one disposition entry referencing the primary finding.
Same rule_id + different values (e.g. 14 different hardcoded passwords) → separate findings, NOT deduplicated.
Different rule_ids that describe the same root cause (e.g. semgrep.python.hardcoded-password + gitleaks.generic.aws-access-key firing on the same line) → dedupe keeping the higher-priority source per the static-analysis skill's priority order.
Stage 5 — Severity calibration
Question: Is the severity consistent across similar findings?
Disposition rules:
Ensure two findings with identical exploitability profiles receive identical presentational severity across the run.
If a finding falls between two severity levels, prefer the higher — better to over-flag than miss. Use exploitability score (0–10) to break ties deterministically per the severity-mapping table in the primitives contract.
Graph-assisted discovery
If the target repo has .codegraph/ (CodeGraph MCP server — codegraph_explore/get_symbol for fast callers/callees/impact lookups) and/or a Repowise MCP server (get_context/search_codebase for semantic search, get_risk for modification-risk context), prefer them over raw Grep/Read when tracing call graphs. This benefits Stage 1 reachability analysis most directly: following inbound calls from a finding location back to production entry points is exactly the callers/impact query these tools are built for, and is especially useful in Joern-absent (LLM-fallback) mode where reachability is otherwise reasoned from grep over call sites. Never assume either tool is present — fall back to Read/Grep/Glob when neither is present; the tools are simply unavailable (no error) on repos without an index.
Finding bypasses an auth check (not merely missing one)
Credential exposure
+2
Finding leaks a credential an attacker could use elsewhere
Input-controlled
+2
An attacker can influence the vulnerable value via request parameters
Persistent
+1
Finding creates persistent state (stored XSS, stored credentials)
Privileged context
+1
Finding runs in an elevated context (root, admin route)
Cascading
+1
A successful exploit unlocks further access (lateral movement)
Rationale field is mandatory (min 20 chars per schema). Summarize which factors applied and why.
Joern integration (when present)
If joern is on PATH, invoke via ${CLAUDE_PLUGIN_ROOT}/skills/false-positive-reduction/tools/reachability.sh (build commands + CPG cache details are in the script). The wrapper detects the target's primary language (C#/.NET, JS/TS, Python, Java, Go — first match wins) and passes the matching joern-parse --language, so non-default frontends like C# build a CPG instead of silently degrading to LLM-fallback; the CPG cache is keyed <sha>.<language>.cpg. The C# frontend needs joern ≥ 2.0.0 plus the dotnetastgen tool — the install scripts provision and verify it (scripts/verify-joern-csharp.sh). Stage 1 reachability queries the CPG for paths from the finding location back to entry points; cite the entry point path in reachability.rationale.
LLM-fallback mode (joern absent)
Stages 1–3 use judgment rather than CPG data; Stages 4–5 work unchanged. Every entry in fallback mode carries reachability_source: llm-fallback. The exec-report-generator detects this and emits a banner — see agents/exec-report-generator.md § Section 0 banners.
Output
A DispositionRegister object per plugins/dev-team/knowledge/schemas/disposition-register-v1.json. Required entry fields and required envelope fields (schema_version, generated_at, dispositioner, reachability_tool, entries[]) are defined in the schema. Written to memory/disposition-<assessment-slug>.json.
Related
agents/fp-reduction.md — the opus agent that implements this skill