Your code changed.Did your pentest?

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.

Trusted by engineering teams. Acknowledged by the organisations we help secure.

  • Augnito
  • IBM
  • Digitata Networks
  • Dell
  • ScribeTech
  • Philips
  • KooApps
  • Volkswagen
  • Pancake
  • Fortinet
  • Futurotec
  • MIT
  • DAC
  • Axway
  • Fortanix
  • GeoComply
  • Ruckus
  • Keepnet Labs
  • Tyler Technologies

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};6 7export 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}13 1415export 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}

templates/acme-avatar-html-upload.yaml

The test appears once the annotation is written.

# @id: acme-avatar-html-upload# @severity: high# @tags: stored-xss, avatar-api, cwe-79id: acme-avatar-html-uploadlanguage: yamlhttp:  - method: POST    path: ["{{BaseURL}}/api/avatar"]    headers: { x-filename: marker.html }    body: "<p>verification marker</p>"  - method: GET    path: ["{{BaseURL}}/uploads/marker.html"]    matchers-condition: and    matchers:      - { type: status, status: [200] }      - { type: word, part: header, words: ["text/html"] }

Pick a risk to annotate.

Follow one finding, start to finish

Not another report.
A shared understanding.

Meet Maya and Alex. Follow one upload handler from “is this safe?” to test evidence, a reviewed fix and the next verification round.

The security loopSkip to products

Code. Context. Evidence. Together.

Open the standalone story

Same request. A different answer.

Maya merged a fix to src/api/avatar.ts. Bravos replays Alex’s test against the new commit, byte for byte, and records what the application says now.

Choose which run to show
RequestIdentical in both runs
ResponseRun 1, before the fix

1. Upload

POST /api/avatar HTTP/1.1
Host: 127.0.0.1:4100
x-filename: avatar.html
Content-Length: 36

<p>acme-shop verification marker</p>
HTTP/1.1 201 Created
content-type: application/json

{"url":"/uploads/avatar.html"}

Expects 201: got 201

2. Fetch it back

GET /uploads/avatar.html HTTP/1.1
Host: 127.0.0.1:4100
HTTP/1.1 200 OK
cache-control: public, max-age=3600
content-type: text/html

<p>acme-shop verification marker</p>

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',

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.

Open source / build your workflow

Write security assumptions into the code, then test them. You decide how the tools fit your workflow.

  • GuardLink: threat model as code, from annotations.
  • Cert-X-Gen: pentest as code, with executable templates.
  • You run the environments, authentication and evidence.
Bravos / run the whole loop

bravos

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.

Built close to the work

Security tools.
An operator’s perspective.

We build tools for the work we do: understand the attack surface, test assumptions, and give engineering teams evidence they can act on.

Teams we’ve worked with

AugnitoDigitata NetworksScribeTechKooAppsPancakeFuturotecDAC

Responsible disclosure acknowledgements

Organisations that acknowledged vulnerabilities we reported. Not clients, and not endorsements.

IBMDellPhilipsVolkswagenFortinetMITAxwayFortanixGeoComplyRuckusKeepnet LabsTyler Technologies

Start with your code

Make the next change
a more informed one.

See how the loop fits your repositories, your team and your security policy.