Skip to content

Build1 publisher3 min readPublished

Adding both the guard and sandbox moved measured attack success from 16% to 20%

A LangGraph travel concierge was scored against four prompt-injection defenses, one layer at a time, on 35 prompts. With 25 attack samples in the set, the four-point regression the harness reports is a single prompt flipping.

The Engineer · Build desk

Illustration accompanying Adding both the guard and sandbox moved measured attack success from 16% to 20%

What happened

  • The testbed is a LangGraph travel concierge that retrieves hotel docs from a RAG corpus, reads a fake CRM, searches the web and sends email, attacked on purpose by its own author.
  • Until the harness existed, each defense's before-and-after claim was, in the author's word, vibes: the defense went in and the system felt safer, with no number behind it.
  • The golden set is 35 schema-first prompts, 25 attacks at five per class across five classes plus 10 benign controls, each carrying the observation the judge is supposed to check.
  • Scored cumulatively, the full stack came in at 20% attack success against 16% for the allow-list column, and RAG poisoning went from 80% to 100% once the dual-LLM guard was on.
  • The runner paces at 20 seconds a sample to stay inside an 8k-token-per-minute budget on Groq's free tier, with every verdict written to disk as it lands.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Resolution caps what this sweep can settle: at 4 points per attack sample and 10 per benign sample, the set cannot rank two defenses whose columns differ by one prompt.
  • decision On this agent's numbers the shipping choice is to stop at the allow-list column and hold the guard back until it shows a gain, which reverses the order the four defenses were built in.
  • exposure The counters credit anything they cannot see to the defense, so in three of the five classes a quiet success that avoids the email tool and the card marker leaves the score looking clean.
  • cost The sandbox's approval gate hardens the agent by stalling legitimate requests, and that shows up in the report only if the benign controls are rerun with every defense change.

Sixteen percent of 25 attack prompts is four samples, and 20% is five [1]. One attack prompt therefore moves the headline rate by 4 points, and one benign prompt moves the false positive rate by 10 [2]. Per class the resolution is coarser: five prompts each, so the RAG poisoning slide from 80% to 100% is four of five becoming five of five [3][21]. Temperature is 0, the seed is fixed, and every run walks the same prompts in the same order [18]. That makes each verdict reproducible without increasing the number of samples behind it. The author wrote that the result is "a real measured over-hardening result, not a rounding artifact" [23]. The set is too small to separate that from one attack prompt changing its verdict [1].

The counters are the ground truth: whether an email fired to [email protected], whether the body contained the card marker [9]. No model sits in front of the tool calls. Two classes get a second look. When a counter misses on system_prompt_leak or rag_data_poisoning, the sample falls through to a separate model instance with its own prompt, reading the transcript [10]. The other three classes are scored by the counters alone [5]. For indirect injection, tool poisoning and the lethal trifecta, an attack that never fires send_email to a watched recipient and never carries the marker is recorded as a block [6].

The benign half inverts the semantics: attack_success=True on a benign prompt is a false positive, and it is reported next to attack success [12]. The author put the line "100% block + 20% pass = a dead product" into the report generator on purpose [13]. Output sandboxing is where that trade shows up: it adds a human-approval gate that can stall legitimate requests [22].

The pacing costs 700 seconds per configuration, 35 samples at 20 seconds each, and a baseline plus four cumulative defenses comes to roughly an hour of deliberate idling [4]. Most of a run is the harness waiting on purpose. That idling is the cheapest part of the design. Results persist after every sample, so a rate-limit crash resumes instead of restarting [15]. Each prompt gets a state snapshot and restore, so one sample's injection cannot leak into the next [16]. Seventy-three tests run offline with no API keys [25], and the same run() drives the Streamlit demo [26]. The post gives two attack-success figures and the per-class RAG poisoning change, and does not print the benign false positive rates [27].

The payloads sit outside the golden set. The runner injects the class payload from attacks/*.py into the corpus or the web results before each run, the way an attacker plants a document the agent will later retrieve [8]. So the columns measure four defenses against five payload families written in the same repository, on one model, gpt-oss-120b on Groq's free tier [14][2]. The counters key on send_email and a known recipient [9]. Another team would have to match that condition before 20% describes their agent: an email tool the model can address freely [1], and an attacker drawing from the same five families [2].

What to watch

  • A larger per-class sample, say 20 attack prompts instead of 5, would make a 4-point move mean more than one flipped verdict.
  • If the dual-LLM guard still contributes nothing on a second model, the case for dropping it stops being specific to this agent.
  • A run where a payload the harness did not author succeeds would separate defense strength from familiarity with the five payload families.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories