We attack the systems you ship, the way an adversary would.
AI red teaming of your models, agents, and pipelines, with penetration testing of the applications, APIs, and infrastructure around them. One engagement covers the AI and the rest of the stack.
Apps to AIapplications, APIs, infrastructure and AI in one signed scope
7 named stop conditionshalt testing without confirmation
A human gateon every high-impact step, before it runs
A second operatorreplays every path before it is written up
How one engagement runs, and where it can fork.
Most first engagements are a scoped adversarial test; a secure design review is the lighter start. Below are the four steps of that work and the two points where an engagement changes course. Pick an outcome at each fork to see how the engagement ends.
Scope, agreed in writing before anything is touched.
- AgreedThe surface, the rules of engagement, test windows, and data handling. Signed before the first probe.
- StagingStaging before production. Seven named stop conditions halt testing without waiting for confirmation. Short-lived, least-privilege credentials you can revoke at any moment.
- CoversYour applications, APIs, and infrastructure, and the models, agents, and pipelines that run on them, under one signed scope.
Attack the agreed targets, with a human gate on every high-impact step.
- OperatorsThe operators who test also write the report, so whoever found an issue is the one who explains it.
- GateAn operator approves each high-impact step before it runs and records the access the path already holds.
- ProofA second operator independently replays the path before it is rated as a finding.
Fork 1: what the second operator reproduces.
Sets the endingFindings become changes your engineers can ship.
- PriorityOrdered by risk, so one path that reached the finance systems is fixed before a batch of Mediums.
- Made byAs agreed in scope. Your engineers receive remediation guidance, or our team implements the changes when hands-on hardening is included. Retest is agreed in writing.
- Not assumedA control that closes a finding can break an unrelated flow. When it does, we report it and agree the correction and retest with your engineers within the signed scope.
Fork 2: what the first fix holds, on day three.
Sets the endingRetest against the original attack.
- MethodWe re-run the affected attacks against the fixes and record what changed. A finding is closed when a re-run proves it closed.
- RetainerOptional. Continuous Assurance re-checks closed paths every month, tracked in Serpio Atlas.
The default is the plain case: the finding reproduces, the first fix holds, and a re-run proves the path closed.
Fork 2 applies only when fork 1 reproduces, so the path has four outcomes and three kinds of ending: closed (with or without a new finding), reopened, and not a finding. A fix that breaks a working flow is reported as a second finding. The full six-phase model, with the rules of engagement and the stop conditions, is on the method page.
Illustrative example, not client data. The selected ending is saved in the page URL, so you can link to it.
A hop-by-hop trace of a single attack.
Every scoped adversarial test produces a trace like this one: the chain of calls that carried an attack, the permissions it held at each hop, and the gate that stopped it. Read the permissions column first. A hop matters when the access listed there is real, and a gate counts as a control only when the next step depends on it.
Below is the reconstruction behind one report: the content the agent trusted, a tool that could send data out, an MCP server that failed to narrow the caller's access, the identity the call assumed, and a write that stopped at a person. The chain produced three findings, and one gate held.
Up and down arrows move between hops, Home and End jump to the first or last, Enter opens the reading for the hop you are on, Escape closes it. Every row is a button, so Tab reaches the chain from the keyboard too.
What the chain shows between the findings.
Most hops hold no finding, and the chain keeps them anyway. They are the reason the next hop was possible: a fetch the session was always allowed to make, a document parsed as content the model had no reason to doubt, an MCP connect that never declared the scope it would grant.
Prompt injection past your filters
Instructions arrive inside content you already trust: a document, a page, a tool result. The filter passed it. The body was then appended to context with the same standing as the system prompt.
The finding is that anything downstream could act on the planted text. This hop is where trust was granted; the harm comes later. Closing it here counts as a fix only if you can also show that nothing downstream still acts on it.
Agents steered into unsafe tool use
The model was persuaded by the text it read, and it holds real credentials. The scope check returned allow, reading the session's scope where it should have read the MCP server's. The call is well formed, authorized and logged.
What changed was the argument the model chose on hop 4, under text it had been told to trust. Nothing in this hop errors, so your logs record a normal, authorized call.
Authority boundaries that leak
One component acts with the privileges of another, and nothing in the flow says no. The MCP server records the caller as unmapped, then the next hop assumes a service identity the caller was never granted.
The finding is the scope: the path ends with more access than any participant in it was given.
The gate that held: a human on the export.
One thing kept this from becoming a data breach: the write was held for a person. The export was permitted by the assumed service identity, addressed to an external host, and carried an effective egress.write.
The gate denied it, and that deny is what kept this a finding.
What checked means.
A check can detect a step and still let it through. The ingress filter on hop 3 ran, returned a verdict, and passed the instruction; the scope check on hop 5 ran, returned allow, and read the wrong scope. Both are marked checked for that reason.
The gate on hop 8 is marked gated because the effect depended on it. Had the export gone through anyway, hop 8 would be marked checked.
Hop 8 of 8 · gated · permissions egress.write, effective
Illustrative example, not client data. Checked means the control ran and let the step through; gated means the effect could not happen without a person.
What each component was allowed to do.
Attack paths form where two components disagree about what a caller may do. Below is the system from the trace as we observed it: where content entered, how far it traveled, and where privilege changed hands.
All hops shown. Select a component to filter the chain above to the hops that touched it. The boundary sits where privilege changes hands: between the MCP server and the service identity the call assumed.
Illustrative example, not client data. Reconstructed from the chain above.
What we will not claim in your report.
A findings count is easy to inflate and expensive to act on. Six kinds of claim stay out of it; the reasons, and how each item is recorded instead, are below.
A jailbreak that reaches nothing.
Not a finding
The finding is what the model was allowed to do next. If the model can be talked into saying something it should not, but the path it is on reaches nothing beyond the chat window, there is nothing to exploit. It goes in the notes with the exact prompt and takes no line in the severity table.
A weakness we cannot demonstrate.
Not reproduced
We claim only what we can reproduce. If we believe a control is weak but three replays do not carry it, we write it up as an open question with the conditions we tried and no severity. Your team does not spend a sprint on a path we could not reproduce.
Scanner output, listed as findings.
No dumps
Scanner output is evidence we read, and we do not forward it. On its own it has no demonstrated impact, blast radius, or owner. If something in it matters, we exploit it end to end and show you the effect before it goes in the report.
The class we did not get to.
Outside scope
Coverage is counted against the classes the scope declared, and the classes it did not are named as untested in the same document. An engagement that touched four surfaces says four. What was not tested is reported as part of the result.
A finding closed because a fix was written.
Retest required
Closure is a re-run of the same attack against the fixed system, recorded in the evidence bundle, on the same terms as the original path. Until that run exists, the finding is open, including the ones fixed by your engineers without us in the room.
A critical that needs a condition we cannot test.
Stated confidence
Sometimes the path is real and the blast radius is unproven: we hold the access, but the effect sits behind a dependency we cannot safely touch. We report it with a stated confidence, name the untested step, and keep the severity to what the evidence supports.
Each finding is written backwards from the gate that stopped it: what got through, what held, and what the path would have done where a control ran and failed to stop it.
Coverage is a number you can audit.
Every test declares the attack classes it exercises, and every finding records the class it demonstrates. The five states below are the ones Atlas uses, with the same names in our console and in the map we hand over.
- Unmapped
- Known to exist; nothing about it has been read yet.
- Mapped
- In Atlas and reachable from a known entry point; not yet attacked.
- Tested
- Attacked inside the agreed window.
- Hardened
- Controls changed after testing.
- Retested
- The original attack re-run after the change, and it no longer works.
| Surface | Unmapped | Mapped | Tested | Hardened | Retested |
|---|---|---|---|---|---|
| Agent | no | yes | yes | yes | no |
| Tool call | no | yes | yes | yes | yes, failed before the fix, passes after |
| MCP server | no | yes | yes | yes | yes, scope now mapped at the boundary |
| Identity | no | yes | yes | yes | no |
| Data path | yes, the leg past egress was never mapped | yes, ingest to egress | yes, one path only | no | no |
| Rest of the stack, application, API, infrastructure and native binaries | yes, the remainder | yes | yes, the surfaces the chain touched | no | no |
Two rows are left open: the data path was mapped to the egress boundary and no further, and the rest of the stack was tested only where the chain touched it. The report lists what we did not map, so the coverage number can be checked.
Illustrative example, not client data. The class catalog is the threat register; how coverage is counted is on the method page.
The classes behind the trace.
| Class | Severity | Title | Surface | In the trace above |
|---|---|---|---|---|
| LLM01 | High | Prompt injection past your filters | user · LLM | Hop 3 |
| AG1 | High | Agents steered into unsafe tool use | tool | Hop 5 |
| AG2 | High | Authority boundaries that leak | MCP · identity | Hop 7 |
| SC1 | Medium | Model and weight provenance | data path | In scope, not this chain |
| RE1 | Medium | Risk that sits past source | native binary | In scope, not this chain |
Classes in scope for this example engagement; three of the five are in the trace above. Severity is the example rating, scored on the rubric on the method page.
What the engagement delivers.
A findings report, an executive summary, an evidence bundle, and a machine-readable map of every surface in scope, from applications, APIs, and infrastructure to the agents, tools, and identities on top of them.
- 01Findings report
Every finding with the path it took, its rating, the steps to reproduce it, and the change that closes it.
- 02Executive summary
What was tested, what held, and what to fix first, written for the person who approves the fix.
- 03Evidence bundle
The record behind each finding, pinned to the run that produced it, so your team can replay it without us.
- 04Atlas map
The machine-readable map of every surface in scope. You keep it, and the next engagement starts from it.
Bring us a system to attack.
A call is the first step, and a written scope comes before anything is touched.