I Built an AI Security Triage Tool That Found a Vulnerability in Itself
Tools called: parse_bandit, deduplicate_findings, score_findings, apply_suppressions, generate_report
Iterations: 6
Stop reason: done
Triage Summary
7 findings total
- 🔴 Medium: 4
- 🟡 Low: 3
Notable Findings
-
parsers/zap.py:14— [MEDIUM] B314 —xml.etree.ElementTree.parseon untrusted XML- CWE-20: Improper Input Validation
- Potential XXE vulnerability
-
core/llm.py:90— [MEDIUM] B310 —urllib.request.urlopenwith an unvalidated scheme- CWE-22: Path Traversal risk
kev/rules.py:158— [LOW] B110 — Bareexcept: passsilently swallowing errors
The Finding That Got Me
The ZAP XML finding was the one that really got my attention.
My security triage tool is designed to parse scanner output. One of the scanners it supports is ZAP, which produces XML reports.
The parser was using Python's xml.etree.ElementTree to process that XML.
That parser can be unsafe when handling untrusted XML because of XXE (XML External Entity) attacks.
In other words, if someone managed to feed P1 a maliciously crafted ZAP report, the parser could potentially be abused to read arbitrary files.
The interesting part wasn't just that the tool detected the issue.
It found a real vulnerability in itself.
The agent:
- Parsed the scanner findings
- Deduplicated them
- Scored them
- Applied suppressions
- Generated a structured JSON report
- Produced a prioritized remediation list
That's the whole point.
Build the tool to automate the boring first-pass work — and then let it tell you where the actual problems are.
This Isn't Replacing Anyone
I want to be clear about what this is and isn't.
This tool doesn't replace a security engineer.
It doesn't replace DAST.
It doesn't do threat modeling.
It doesn't understand business context.
What it does is compress the boring part of the job — the mechanical first-pass triage — so a human can spend more time on the decisions that actually require judgment.
And honestly, that's one of the biggest things I learned from building it.
The automation isn't the hard part.
Understanding the security workflow is.
Building It Forced Me to Understand the Entire Pipeline
I had to understand how each scanner actually represents its findings.
For example:
- Semgrep → JSON
- Bandit → JSON
- ZAP → XML
- Trivy → JSON
- Nuclei → JSONL
They're all different.
Then there was the question of deduplication.
What's a good deduplication key when the same vulnerability can be reported by multiple scanners?
It turns out rule_id + file + line isn't necessarily good enough because rule IDs can diverge across tools.
A better starting point is something like:
CWE + file + line
Then there's risk scoring.
How do CWE-based heuristics translate into an actionable severity score?
And where do those heuristics break down?
Then suppressions.
How should suppressions.yaml work without becoming a mechanism for silently hiding real vulnerabilities?
And finally, the LLM layer.
What can an LLM reliably identify as a false positive?
What shouldn't it be trusted to decide?
These aren't problems you can completely shortcut by using someone else's enterprise triage platform.
You have to understand the process to know what you're automating.
Architecture
For the technical readers, here's the current architecture.
agent-core (shared library)
├── Agent
│ └── iterative tool-calling loop
│ ├── max_iterations
│ └── policy enforcement
│
├── ToolRegistry
│ └── @tool decorator → schema → LLM tool spec
│
├── ExecutionPolicy
│ ├── READ_ONLY
│ ├── SAFE
│ ├── AUTO
│ └── HUMAN_CONFIRM
│
├── LLMProvider
│ ├── ClaudeProvider
│ └── OllamaProvider
│
└── AgentTracer
└── JSONL per-call observability
p1-sast-dast-triage
├── agent/tools.py
│ └── 10 @tool functions + session store
│
├── agent/triage_agent.py
│ └── TriageAgent
│ └── wraps agent-core.Agent
│
└── main.py
└── existing CLI
├── --agentic
└── --provider
Key Design Decisions
1. ExecutionPolicy.SAFE
The LLM can call read-only analysis tools and write reports.
It cannot execute arbitrary shell commands.
That's an important boundary for an agent operating on security findings.
2. Session Store
Findings live in memory and are keyed by session_id.
The tools return summaries rather than dumping the entire underlying dataset into every model interaction.
This keeps the agent context smaller and makes the tool interface more predictable.
3. Backward Compatible
The existing main.py pipeline remains untouched.
The agentic functionality is additive:
--agentic
--provider
You can use the existing deterministic pipeline or opt into the agentic workflow.
What's Next?
P1 is part of a 5-project security tooling series.
- P1 — SAST/DAST Triage → this project
- P2 — Threat Model Generator → done
- P3 — AI Log Anomaly Detector → done
- P4 — AI Pentest Report Assistant → done
- P5 — Autonomous HTB Pipeline → in progress
Each project reuses agent-core.
The bigger goal is to build an agentic security research toolkit where tools compose.
Instead of building five completely independent AI security tools, the idea is to build reusable agent infrastructure and then compose specialized security workflows on top of it.
What I Learned
The most interesting result wasn't the number of findings.
It was the feedback loop.
I built a tool to help find vulnerabilities.
The tool found a vulnerability in the tool.
That forced me to look at the implementation from the perspective of an attacker rather than just the perspective of the person building the automation.
And that's probably the most useful property an automated security tool can have:
It should make you better at seeing the things you might otherwise miss.
Code
The projects are open source:
If you're building agentic security tooling, I'd be interested in hearing how you're handling tool permissions, false-positive filtering, deduplication, and human-in-the-loop decisions.
Top comments (0)