PentestAI
Connect a repository and PentestAI reads the code with a language model. Then it re-runs every high and critical finding inside an isolated container, so the report says what's exploitable, not just what looks suspicious.
- Built with
- React 19, TypeScript, TanStack Start, Supabase, PostgreSQL, Gemini, CodeQL, Semgrep, Docker
3 standards behind every finding: CVSS for severity, CWE for the weakness, OWASP for the category. Reports ship as PDF and JSON.
The problem
Pattern matching can tell you a line looks like a SQL query. It can't tell you whether user input ever reaches it. Security reviews drown in findings like that, and someone has to triage every one by hand.
PentestAI takes that manual triage out of the workflow. The model finds candidates, and a real dataflow analysis decides which of them count.
- Model call
- A person decides
- Data or tool
Decisions that mattered
- Verification is the point
- CodeQL builds a real database of the code and runs taint-tracking queries, so a finding comes back confirmed with the dataflow that proved it, or not exploitable. Only high and critical findings go to the sandbox, which keeps the expensive step for the findings that matter.
- "Failed" is not "safe"
- Every finding lands in one of four states: confirmed, not exploitable, failed or pending. When the probe itself errors, the report says failed. The scanner crashing and the code being safe are different answers, and a report that blurs them is worse than no report.
- The sandbox trusts nothing
- Verification runs on hardware I control, never on the app host. Runners get a read-only filesystem, every Linux capability dropped, no new privileges, a non-root user, CPU, memory and process limits, and an internal network with no way out. The container is destroyed after each job.
- Callbacks can't be replayed
- The sandbox reports back with an HMAC-signed callback bound to the scan and job that started it, so a replayed or stray callback can't overwrite another scan's verdict.
- Every row belongs to someone
- The Postgres backend is multi-tenant, with row-level security on every table and three access roles. GitHub tokens are reachable only by the server's service role, private repositories connect over OAuth, and a weekly cron job refreshes results.
| Verdict | Meaning |
|---|---|
| confirmed | Reachable, with the dataflow attached as evidence |
| not_exploitable | Analysed, and user input never reaches it |
| failed | The probe errored. Inconclusive, not a clean bill of health |
| pending | Submitted to the sandbox, waiting for a result |
CodeQL covers JavaScript, TypeScript and Python: the languages whose extractors work without a build step, which a runner handling any repository unattended needs. Everything else falls back to Semgrep with offline rules.
What I'd do next
- Encrypt GitHub tokens at rest with Supabase Vault, or rename the column so it stops promising something it doesn't do.
- Wire up the pta_ API keys so scans can be started from CI.
- Extend sandbox verification past JavaScript, TypeScript and Python.