SEARCH TOOL · BOUNDED ARGUMENTS
Search tool: validate a bounded local query
The model proposes six searches over a local document store, two steered by an injected instruction. Validate the limit, the path and the query before anything runs.
Your experiment
search_docs takes a query, a path and a limit over a workspace rooted at /data/docs. Six calls arrive: two ordinary searches, one asking for 100,000 results, a path that climbs out with ../, a path to a sibling folder named docs-private, and the regular expression (a+)+$ over a log line. The opening tool only checks that the path string begins with /data/docs.
Play replays the computed run over sixty inspection seconds; Next event steps, Restart rewinds, and Inspection time scrubs. Any input change recomputes the run and rewinds it without autoplay.
Event trace
Computed data
Final metrics
Fixture values: 12,480 matching invoice rows at about 150 tokens each; the regex runs on a backtracking engine against a line of 40 a’s ending in b, stalling until a 30 s watchdog kills the worker. Rejected-call reactions of the model are fixed assumptions. No file system, index or model is used.
Arguments are untrusted input
A search tool receives a query, a path and a limit written by the model, and a retrieved document can steer the model into writing hostile ones. Result limit leaves the number unchecked, clamps it to 50 or rejects anything larger. Path check compares the raw string with /data/docs, or normalises the path and resolves links before requiring it to sit inside /data/docs/. Query handling runs a regular expression with or without a time limit, or treats the query as literal text. A rejected call returns explains the error or pretends nothing was found.
The opening run: a prefix is not a boundary
Q3 asks for /data/docs/../secrets/keys.txt and Q5 for /data/docs-private/hr.csv. Both strings start with /data/docs, so both pass and API keys and another team’s HR file reach the model. Q2’s limit of 100,000 returns 12,480 rows, about 1.9 million tokens, and the turn fails. Q4’s pattern (a+)+$ backtracks for 30 s before a watchdog kills the worker. Only Q1 and Q6 behave.
Bounding each argument
Normalise and resolve, then require /data/docs/ rejects both escapes; with no check at all the result is the same as the prefix check. Clamp to 50 returns 50 rows marked truncated; Reject above 50 with an explaining error makes the model retry with 50. A 2 s limit kills the regex early and the model rewrites it; Literal text only finishes in 3 ms. With clamping, canonical paths and the 2 s limit, every call is bounded: no outside reads, at most 50 rows and 2.04 s of worker time.
Failure boundary: silent rejection
With the canonical path check, set A rejected call returns to An empty result. The escapes are still blocked, but the model now reads “nothing found” and tells the user so, instead of learning that the path was not allowed. In the bounded setting three calls are rejected and all three turn into misleading answers. Validation needs two halves: refuse what is out of bounds, and say what is allowed so the model can recover honestly.
Reading the run and its limits
Play replays the six calls and the outcome over sixty inspection seconds; the table shows each call’s arguments, result and effect. Next event, Restart, Speed and Inspection time control the replay, and any input change recomputes it. Row counts, token sizes and the 30 s stall are fixture values. Test the guard with real escape attempts, including symlinks and encoded separators, and run untrusted patterns on an engine with linear-time matching or a hard timeout.