DEV Community

Cover image for I Tried to Sneak Four Bad Agents Past My Own Certification Gate. All Four Got Blocked.

I Tried to Sneak Four Bad Agents Past My Own Certification Gate. All Four Got Blocked.

Debashish Ghosal on October 01, 2026

Last week I spent a day trying to defeat software I wrote myself. Not a red-team exercise I scheduled for optics — four agents I built specificall...
Collapse
 
reidmarlow profile image
Reid Marlow •

The plausible agent that comes back wrong is much harder to recover from. A runaway loop burning tokens trips hard budget ceilings or rate limits within minutes, so the blast radius is bounded by money. An agent that emits clean, well-formed JSON without ever calling the prerequisite read tool poisons downstream databases and issues quietly. Verifying the action trajectory against required tool calls before looking at the generated text is the only way to catch that before an operator assumes a green exit code means real work happened.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks, Reid — that's attack 3 in your words. The regressed-agent returned well-formed JSON every time; a demo and a human eyeballing the output would both pass it. The deterministic action_audit check failed it precisely because it never called mcp.github.read_issue — behavior, not text. "A runaway loop is bounded by money; the plausible agent quietly poisons downstream data" is exactly why the corpus carries negative tasks at all.

Collapse
 
cailab profile image
CAI •

Great field test. The seam model is the key insight here, putting each gate at the point where the decision is still cheap makes the whole system auditable in a way that permissive admission followed by retroactive detection never is.

The budget attack is where this gets especially interesting. You note that admission can only judge headroom while a per-run ceiling needs cost accrual first. There is a middle path: let the agent carry a capability token that proves pre-authorized spending authority up to a declared ceiling before the run starts. The gate checks the token at admission instead of waiting for the usage report. The seam moves earlier.

The model swap gate is the other standout. Binding the attestation to the exact model identity closes a hole most people do not realize is there.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks — that's a genuinely good design push. You've named the exact tension: admission can only judge day/team headroom, so the per-run ceiling has to wait for cost to accrue, and I block at the usage report for that reason. A pre-authorized capability token lets admission check the authority before the run and still enforce the ceiling later — moving the money seam earlier without waiting for spend. I want to sit with whether that's a second gate or an extension of the manifest's budget block; either way it's the right direction.

Collapse
 
hannune profile image
Tae Kim •

The model swap gate stings because I'd done the same thing to myself with entity resolution: switched embedding models mid-project and all my thresholds were suddenly miscalibrated, with no signal that anything had moved. Took a while to realize the distribution had just shifted. I ended up binding the calibration run to the model version so any swap forces a re-tune, which is roughly what your attestation is doing. The regression case is the one I'd lose sleep over though, because action_audit works here where the required tool call is specified, but most pipelines I've seen don't define that upfront and the plausible-but-wrong output just turns into a quiet data quality problem downstream.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks — your entity-resolution story is the same bug with embeddings instead of a prompt, and the threshold miscalibration with "no signal that anything moved" is exactly the silent part. That's what the Ed25519 attestation is doing here: it's bound to the exact model identity and verified on read, so a swap can't ride on the old certification — it has to re-tune. And yes, the regression case you'd lose sleep over is the real one; action_audit only works because the required tool call is declared up front.

Collapse
 
presango profile image
Priya Nair (Presango) •

The line that stuck with me is that the refusal text is a product surface. We learned the same thing from the other side: when an agent presents or answers in front of people, the moment it says "that is not in my material" is the one that builds or kills trust, and the wording of that refusal matters more than any correct answer around it. Did you find your four attackers taught you anything about the order of the gates, i.e. which check should fail first so the operator gets the most useful message?

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks — "the refusal text is a product surface" is the line I built the whole thing around, and your "that is not in my material" example is the human analogue. On ordering: each gate fires at the cheapest seam (admission before persistence, budget at the usage report, shaping before the agent's context), but I haven't formalized a global precedence across gates — so today the operator gets whichever gate trips first, which is not the same as the most useful message. That's a fair gap and a good next design question.

Collapse
 
rishita_sharma_b0aa1ff81a profile image
Rishita Sharma •

The admission-time refusal and binding certification to the exact model are strong design choices. I especially like treating refusal text as part of the workflow instead of a generic error. In systems I’ve worked around, teams often log denials but expose too little context for the operator to fix the issue quickly. Testing model swaps, stale attestations, and replay attempts feels much closer to production reality than another happy-path benchmark. This is a practical pattern for making agent autonomy earned rather than assumed.

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks, Rishita — that's exactly the intent: make autonomy earned rather than assumed. The four negative fixtures took as much design as the two good ones, and the refusal text carries the workload, context, current status, and required status so an operator can act without reading source. Logging denials is the easy half; giving the operator enough to fix it is the half most systems skip, and you've named it.

Collapse
 
mudassirworks profile image
Mudassir Khan •

the "anyone can build a platform that starts runs, i wanted to know if mine could say no" line is exactly the framing our eval harness team argues about every week. most gate designs i've seen fail on the edge between certification time and deploy time — the agent passes in staging, configuration drifts by 200ms in prod, and the gate has no signal for that.

curious what your replay buffer looks like for rejected runs. do you log the refusal reason alongside the gate that produced it, or just the status? we found that logging the specific invariant that failed (not just the gate name) cut our false positive rate by about 30% over six weeks. what triggered attack 4 specifically?

Collapse
 
debashish_ghosal profile image
Debashish Ghosal •

Thanks — great questions. The refusal is recorded with the workload, context, status, and required status (not just the code), and it lands in the tamper-evident audit chain, so you can reconstruct which invariant failed rather than just the gate name. Attack 4 (budget-probe) was triggered by pricing the local omlx model via HIVEPLANE_BUDGET__PRICES (150/600 USD per 1M tokens) and dropping the zero-cost exemption — otherwise a $0 model means the ceiling is never reachable. Your "certification time vs deploy time drift" edge is the drift detector's whole job, and it's now shipped.