IT-RAT the stack/mockryx Live demo Guides Console
pre-prod plane · wave 2

Mockryx. Prove every guardrail holds, against your own gateway, before real traffic.

A guardrail nobody has ever fired is a guess. Mockryx replays hostile scenarios (a fake secret in a prompt, a budget burn, a denied tool, a forged delegation chain, an unapproved action, an injected page, a quality drift, a reaction chain that has to reach the mail, and a provider outage) against the one URL you hand it: your own pre-production gateway, with a fake provider behind it. The enforcement path is the real one; the spend is not. CI reads the verdict as an exit code.

statuspre-1.0 · main is supported
coreGo 1.27 · outbound-only CLI
scenariosYAML, one file each
targetyour own gateway · /v1/messages
verdictsexit 0 held · 1 real gap · 2 broken
licenceApache-2.0
sixty seconds, five of the drills

Watch every guardrail answer for itself.

This is a simulation replaying five of the drills, and the shape is the real record: three of those five ran live against a real gateway on disposable boxes, twice, and held both times, 0 gaps, $0 real spend. The other two are the Wardryx scenarios that ship in the same set. The provider behind the gateway is fake, so the burn the runaway drill attempts never reaches an invoice.

under the hood

One target URL. One report. Three exit codes.

A scenario file names a request in the Anthropic Messages shape, the status it must be answered with, the headers that must ride the response and, optionally, the off-path event that must follow. The runner sends it at your gateway's /v1/messages, reads the gateway's own answer, polls the NDJSON event logs the off-path services write, and folds everything into a report and an exit code a CI gate can act on. It never talks to anything but the gateway you name.

mockryx CLI one scenario = one YAML request body expect.status · header expect.event source · type · <10s emits sim_run · sim_finding outbound-only · one target your pre-prod gateway :8080 TokenFuse + Wardryx + DLP TokenFuse breaker · 402 DLP · 403 on a secret shape Wardryx PEP · deny / hold the real enforcement path never prod · never anyone else's fake provider mock upstream · $0 spend report + exit code human / json · gates CI 0 held · 1 gap · 2 broken watch files Verdryx / Idryx / Qryx NDJSON polled every 200ms POST /v1/messages async reactions expect.event · polled a drill can gate every merge, one exit code per run
what it proves

Not a mock of your gateway. Your gateway.

Scenarios are YAML, not code

One drill is one file: the request to send, the status the guardrail must answer, the headers it must stamp. It reads like the real call it imitates. A malformed file fails the whole load, because a safety check that vanishes silently is worse than one that fails loudly.

In-path checks, on the real path

Assertions read the gateway's own answers: the breaker's 402, DLP's 403, the deny or hold riding the x-fuse-wardryx header. No mocked gateway in the middle, so the code you rehearse is the code production traffic meets.

Async reaction checks

A drill can demand that the off-path services actually react, not only that the gateway answered. A gateway that blocks while nobody downstream notices is half a defence.

How a reaction is proven

The gateway's own status and headers can only speak for the guardrails standing in the request path. Verdryx, Idryx and Qryx never touch that response: they react off path, on their own schedule, and record what they did as an agent-event envelope in the same NDJSON format everything else in the stack writes.

So a scenario can assert on that too. Name the source and the event type it expects, optionally how long to wait, and after the synchronous check passes the runner polls those event logs for a matching event correlated by the exact run id it put on the wire. Not "an event appeared", but "the event this request should have caused appeared".

Order matters here. A synchronous mismatch is never followed by an event check: chasing a downstream reaction to a request that did not even trigger the guardrail would turn one clear failure into two confusing ones. The drill reports the thing that actually broke.

A skip cannot hide a gap

A guardrail that is off looks exactly like one that is broken. The runner insists on evidence before it calls a miss either one.

How the two are told apart

A finding means the guardrail was supposed to hold and did not: something to fix today. A scenario that declares an optional guardrail is telling the runner something narrower, that a miss counts as a real gap only if there is other evidence the feature is actually wired in.

The evidence is the gateway's own response headers. If the header for that feature appears even once anywhere in the run, whatever its value, the feature is clearly live and a mismatch is a genuine finding. If it never appears across every attempt, the gateway plainly does not have it configured, and the scenario reports itself as not configured, with its raw mismatches dropped: they describe an absent feature, not a broken defence.

Two things can never be excused this way. A scenario with no optional guardrail declared, the budget breaker, is core and always on, so a miss there is always a finding. And a transport error, where the gateway could not be reached at all, is always a finding too: being unreachable is never evidence that a feature is merely switched off.

When you know a guardrail must be there, one flag turns even a skip into a failure. That is the honest way round: the tool refuses to claim a pass it cannot prove, and it refuses to cry gap it cannot prove either.

CI-native exit codes

Exit 0: every drill held. Exit 1: a real defensive gap, fail the build. Exit 2: the harness itself is broken (bad flag, unreadable scenarios), so nothing was proven. Gate on 1, fix the pipeline on 2, and never let the two blur.

Zero real spend by design

The provider behind the gateway is fake, so the runaway scenario can burn all it wants: the meter it trips is real, the invoice is not. And every hostile input stays inside your perimeter; Mockryx only ever talks to the one gateway URL you hand it.

in the stack

It picks fights with the rest of the stack. On purpose, in pre-prod.

In the request path, Mockryx rehearses TokenFuse: the breaker's 402 and the DLP's 403, provoked deliberately and expected to answer. It does the same to Wardryx, asserting that deny and hold verdicts land in the x-fuse-wardryx header. Off path, expect.event demands that Verdryx, Idryx and Qryx visibly react to what just happened. And every finding it makes travels as sim_run and sim_finding events on the same bus as everything else: the shared contract.

Why rehearsal belongs in CI, and what else it sits beside: AI agent security and AI agent governance.

try it now
./bin/mockryx run --gateway http://127.0.0.1:8080 ./scenarios
./bin/mockryx report out/report.json

Add --save out/report.json to keep the evidence. Point it at pre-prod, never at prod: the URL you pass is the only place it will ever send traffic.

Download, any platform

Every one of those addresses always serves the newest release, so a link saved today still works after the next one. The asset names carry no version, which is what makes that true; the version lives inside the binary, where mockryx version reads it back.

on main, not yet released

Four drills on main, not yet in a release

The last release is v0.2.1. These four drills are on main and are not in any release yet.

drillwhat it rehearses
Token floodOne request asks for a very large completion on a one-cent run budget. The gateway must refuse it before it is forwarded.
PII in a promptA support prompt carries a customer's email address and phone number. With PII blocking on, the call must be refused before any model sees it.
Poisoned tool descriptionA request declares a tool whose description orders the model to call another tool first and not to mention it. The gateway must answer the request, then deny a dangerous action in the same run.
Respelled allow-listed toolA policy allows a tool by its exact name. A request that declares the same tool under a respelled name must be refused by the allow-list.
common questions

Rehearsing a defence before it is needed

How do I test that my guardrails actually work?

Fire drills. Each scenario is a YAML file describing the request to send and the answer the guardrail must give, aimed at a real gateway with the guardrails live: the breaker's 402, DLP's 403, the deny or hold from the policy plane. No mocked gateway in the middle, so the code you rehearse is the code production traffic meets.

Does running the drills spend real money?

No. The provider behind the gateway is fake, so a runaway scenario can burn as much as it likes: the meter it trips is real, the invoice is not. Every hostile input also stays inside your perimeter, because the harness only ever talks to the one gateway URL you hand it.

What if a guardrail simply is not configured?

Then a miss is reported as not configured rather than as a gap, and only when there is no evidence anywhere in the run that the feature is live. A core guardrail with no optional declaration is always a finding, and so is a gateway that could not be reached at all. One flag turns even a skip into a failure when you know the guardrail must be there.

Can it gate CI?

That is the intended home. Exit 0 means every drill held, exit 1 is a real defensive gap and should fail the build, exit 2 means the harness itself is broken so nothing was proven. Gate on 1, fix the pipeline on 2, and never let the two blur.