Problem
Raw scanner output is noisy. The same SQLi vulnerability at app.py:42 shows up in Semgrep and Bandit as two separate findings. Security teams waste hours deduplicating and filtering before they can triage what matters.
What It Does
- Normalize โ parse Semgrep JSON, Bandit JSON, and ZAP XML into one schema with
rule_id,cwe,file,line,severity,snippet - Deduplicate โ key on
{cwe}:{file}:{line}. Same vulnerability, different scanner = one finding - Score โ risk score 0โ10 based on severity + finding source (dynamic findings boosted) + CWE weight (SQLi/XSS/path traversal get extra)
- Filter FPs โ local LLM (hermes3:70b on Ollama) reviews each finding with code context โ
status: confirmed | needs_review | likely_fp - Export โ SARIF 2.1.0 with suppressions block for
likely_fpfindings โ direct upload to GitHub Code Scanning
Key Technical Decisions
CWE-based dedup over rule-name matching โ scanners use inconsistent names but share a CWE taxonomy. CWE-89:app.py:42 is the same issue regardless of which tool found it.
Local LLM only โ all code stays on-prem. The model sees: rule, CWE, severity, file path, 10-line snippet. Returns structured JSON verdict. No cloud calls.
SARIF suppressions โ likely_fp findings are preserved in the output but marked as dismissed. The GitHub dashboard stays clean; the data isn’t lost.
Results
On a test Django app (Semgrep + Bandit + ZAP): 400 raw findings โ 180 deduplicated โ 142 after FP filter. ~65% reduction in triage burden.