Pentest your app in minutes.
One API call starts an autonomous pentest. Poll it for live agent activity and findings, each with a proof of concept and a ready-to-merge fix.
Quickstart
Make sure Docker is running and your environment is set, then start a scan and poll it.
1. Configure
# .env.local
RS_MODEL=gemini/gemini-4-argon
GEMINI_API_KEY=your-gemini-api-key2. Start a scan
curl -X POST http://localhost:3333/api/scan \
-H "Content-Type: application/json" \
-d '{"targets":["https://api.example.com"],"targetType":"url","mode":"quick"}'
# → { "runId": "api-example-com_m1a2b3", "status": "running" }3. Poll for progress & findings
curl "http://localhost:3333/api/scan/api-example-com_m1a2b3"Authentication
The API routes are currently unauthenticated — there is no server session or API key yet. In the browser, the app uses a client-side flag only to personalize and gate the UI; it does not protect the routes.
/api/scan*.Start a scan
/api/scanStarts a pentest in the background and returns a runId immediately.
Body
targetsstring[]requiredOne or more targets. Blank entries are dropped.
targetstringoptionalSingle-target convenience; used if targets is empty.
targetTypestringoptionalurl · repo · dir · whitebox · list. Default url.
modestringoptionalquick · standard · deep. Default quick.
llmstringoptionalPrimary model, LiteLLM format. Default gemini/gemini-4-argon. Fallbacks follow automatically.
instructionsstringoptionalScope notes, credentials or focus passed to the engine.
schedulestringoptionalmanual · weekly · monthly · daily · continuous. Default manual.
Example
const res = await fetch('/api/scan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
targets: ['https://api.example.com'],
targetType: 'url',
mode: 'quick',
}),
});
const { runId } = await res.json();Poll a scan
/api/scan/:runIdReturns a live snapshot. The dashboard polls this every 2 seconds until status is complete or error.
Query
agentstringoptionalWhen set to an agent id in the run, includes that agent's steps. Omit to skip the step read.
Response
{
"runId": "api-example-com_m1a2b3",
"status": "running", // running | complete | error
"progress": 62,
"findings": [ { "id", "title", "severity", "cvss", "endpoint", "poc", "patch" } ],
"agents": [ { "id", "name", "status", "parentId", "task", "skills", "depth" } ],
"activeAgents": 3,
"steps": [ { "seq", "kind": "tool", "tool": "browser_navigate", "text": "..." } ],
"counts": { "critical": 1, "high": 3 },
"logs": [ "..." ]
}Polling loop
const timer = setInterval(async () => {
const data = await fetch(`/api/scan/${runId}`).then(r => r.json());
render(data.agents, data.findings, data.progress);
if (data.status === 'complete' || data.status === 'error') clearInterval(timer);
}, 2000);List scans
/api/scanReturns every scan, newest first.
{
"scans": [
{ "runId": "...", "target": "https://api.example.com", "targetType": "url",
"mode": "quick", "startedAt": "2026-10-09T10:00:00.000Z", "status": "complete" }
]
}Agents & steps
RedSuture runs a graph of specialized agents (recon, exploitation, validation, remediation). The poll response includes the live agents tree; request one agent's steps to see exactly what it's doing.
Agent shape
interface Agent {
id: string;
name: string; // "Recon", "Exploitation", ...
status: string; // running | waiting | completed | failed | ...
parentId: string | null; // tree edges
task: string | null; // what it's doing right now
skills: string[]; // ["bola", "sqli", "proxy"]
depth: number; // indentation in the tree
}Fetch an agent's steps
curl "http://localhost:3333/api/scan/<runId>?agent=exploit"
# steps: [ { kind: "message" | "tool" | "output", tool?, role?, text } ]node:sqlite). On older Node the agent tree still works; steps come back empty.Open a fix PR
/api/github/prBuilds a pull-request payload for a finding's patch.
Body
repoUrlstringrequiredhttps://github.com/owner/repo or owner/repo.
targetBranchstringoptionalDefault main.
vulnIdstringoptionalUsed in the generated branch name.
vulnTitlestringoptionalUsed in the PR title.
patchDiffstringoptionalThe patch to attach.
Models & environment
The engine reaches 100+ model providers. Set the primary model, a key for each provider you use, and an optional fallback chain. Models whose provider has no key are skipped.
# Primary model (LiteLLM format)
RS_MODEL=gemini/gemini-4-argon
# One key per provider (RS_API_KEY is a catch-all)
GEMINI_API_KEY=...
MINIMAX_API_KEY=...
DEEPSEEK_API_KEY=...
OPENROUTER_API_KEY=...
ANTHROPIC_API_KEY=...
# Optional: comma-separated backup models, tried in order on provider failure.
# Unset → gemini-4-argon → minimax-m3.1-flash → deepseek-v4.1-flash → glm-5.3 → claude-opus-5
RS_MODEL_FALLBACKS=
# Optional: path to the engine binary
RS_ENGINE_BIN=Each scan tries the requested model first, then each fallback — every attempt is a real scan. Pass llm on the request to override the primary per scan.
Scan engine
Each scan runs RedSuture's autonomous engine in an isolated workspace. The engine writes its results to a per-run directory that the poll route reads back.
- Every run produces SARIF 2.1.0 findings, a live agent graph, a per-agent step stream, and logs.
- A run with no findings and a run with findings are both successful completions.
- If the engine is unavailable or every model fails, the run is marked
error— never a fabricated result. - Run output stays on your machine and is git-ignored.