Skip to content
Harshith Pramod
All work

Episode 01 · The Door Left Open 開いた扉 · Personal project, 2026

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
Links

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.

How a repository moves through PentestAIGitHub repo to Secret scan; GitHub repo to LLM analysis; LLM analysis to Sandbox (high and critical only); Secret scan to Verdict; Sandbox to Verdict; Verdict to Report; Verdict to Youhigh and critical onlyGitHub reporead-only cloneSecret scanruns firstLLM analysisGeminiSandboxCodeQL taint trackingVerdictHMAC-signed callbackReportPDF and JSONYoutriage what's left
How a repository moves through PentestAI
  • 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.
What a finding can come back as
VerdictMeaning
confirmedReachable, with the dataflow attached as evidence
not_exploitableAnalysed, and user input never reaches it
failedThe probe errored. Inconclusive, not a clean bill of health
pendingSubmitted 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.