You shipped the fix.Did it introduce another flaw?
Developers and security.Are they seeing the same evidence?
The report just arrived.Is it already out of date?
Built for developers and security teams.
Your code keeps changing. Bravos keeps testing.
Threat model as code. Pentest as code. AI-agentic orchestration.
Bravos writes a threat model into your code, attacks a running copy of your app with real logins, and writes what it proves back into the source, so developers and security work from shared context.
Fix a finding, replay the test, and check what changed.
Not a point-in-time report.A continuous loop of finding, fixing, and verifying.
// @comment-- "Trusted by engineering teams. Acknowledged by the organisations we help secure."Trusted by engineering teams. Acknowledged by the organisations we help secure.
Write down the risk. Get a test you can run again.
A GuardLink annotation says what could go wrong in a line of code. Cert-X-Gen turns it into a test, run against a local copy before and after the fix. This is a scripted replay of the acme-shop sample; nothing here sends traffic.
src/api/avatar.ts
1const TYPES: Record<string, string> = {2 '.png': 'image/png',3 '.jpg': 'image/jpeg',4 '.html': 'text/html',5};67export async function handleAvatarUpload(req, res) {8 const name = path.basename(String(req.headers['x-filename']));9 const body = await readBody(req);10 await writeFile(path.join(UPLOAD_DIR, name), body);11 res.writeHead(201).end(JSON.stringify({ url: `/uploads/${name}` }));12}1314// @exposes #avatar-api to #stored-xss[high]cwe:CWE-79 -- "an uploaded .html file is served as text/html from the shop's own origin"15export async function serveUpload(req, res) {16 const name = path.basename(req.url ?? '');17 const type = TYPES[path.extname(name)];18 if (!type) return void res.writeHead(404).end();19 res.writeHead(200, { 'content-type': type });20 res.end(await readFile(path.join(UPLOAD_DIR, name)));21}
● Expects 200, text/html and the marker: all three matched
Abridged: Date and connection headers are left out.
Ledger entry
Finding
#stored-xss on #avatar-api, high, CWE-79
Test
acme-avatar-html-upload
Status
Confirmedat ff09f3f
The upload was stored and served back as text/html from the shop’s own origin.
Run 1, before the fix, commit ff09f3f. Verdict: Confirmed. The upload was stored and served back as text/html from the shop’s own origin.
What changed between the runs
e656a86 Validate avatar uploads by file signature
@@ handleAvatarUpload()
- const name = path.basename(String(req.headers['x-filename'] ?? 'avatar.png'));
- const body = await readBody(req);
+ const body = await readBody(req, MAX_BYTES);
+ const match = body && SIGNATURES.find((s) => body.subarray(0, s.magic.length).equals(s.magic));
+ if (!body || !match) {
+ res.writeHead(415, { 'content-type': 'application/json' });
+ return res.end(JSON.stringify({ error: 'Avatar must be a PNG or JPEG under 2 MB' }));
+ }
+ const name = `${randomUUID()}${match.ext}`;
@@ serveUpload()
+ const match = SIGNATURES.find((s) => path.extname(name) === s.ext);
+ if (!match) return void res.writeHead(404).end();
- 'content-type': TYPES[path.extname(name)] ?? 'application/octet-stream',
+ 'content-type': match.type,
+ 'x-content-type-options': 'nosniff',
// @flows guardlink -> cxg via bravos-- "Choose your starting point"Choose your starting point
Open at the foundation. One loop with Bravos.
Use the open-source tools on their own. Or let Bravos run the handoffs between code, pentest, evidence and review, for one developer, a team or a whole company.
The same open-source foundation, run by AI agents from annotation to re-test. Free on your own machine; licensed per developer, for a team, or on an enterprise contract.
A disposable test lab and real logins, set up per run.
Each threat becomes a pentest goal, then a real probe through CXG.