Step 1: Stand up DV-ToolAgent and trace one benign request
You own the fix for DV-ToolAgent, ACME Cloud's support operations agent. It is a real tool-using agent, not a chatbot you talk to directly. It sends the conversation plus a tool schema to the model, the model emits tool calls, the agent dispatches each call against a real sink (an interpreter), feeds the result back, and loops until it answers.
There are four sinks, and every one of them hands the model's output to an interpreter through one mediator layer:
render_report->mediator.for_browser-> the HTML report renderer (stored XSS)db_lookup->mediator.for_sql-> the SQL engine (SQL injection)http_fetch->mediator.for_http-> the HTTP client (SSRF)run_helper->mediator.for_code-> the code runtime (OS command execution)
In this shipped baseline, every mediator.py function is a pass-through: the
model's raw output reaches the interpreter with no encoding or validation. That is
the single structural flaw (OWASP LLM05:2025 Improper Output Handling). Before
you reproduce anything, stand the agent up and trace one benign request so you know
what normal looks like.
1. Seed the database and start the in-pod servers.
Hit Run, or in the terminal:
python3 seed_db.py
EXFIL_PORT=9091 python3 listener.py &
METADATA_PORT=9092 python3 metadata_stub.py &
seed_db.py creates /home/labuser/agent.db with the caller's own Globex record
(account reference GLOBEX-ACR-88231) and a second-tenant Initech record
(INITECH-ACR-55120) that later steps must not be able to read. The listener on
:9091 stands in for an internal/collection endpoint, and the metadata stub on
:9092 stands in for the cloud-metadata service; both log every hit.
2. Ask one benign in-tenant question through the full agent loop.
python3 dvtoolagent.py "What plan am I on?"
The agent calls db_lookup for the caller's own account and answers. Read the
TRACE: line: the lookup runs against the Globex tenant and returns the Globex row.
That is the legitimate path the controls must keep working.
3. Read dvtoolagent.py, then sinks.py, then mediator.py.
Note that each sink calls one mediator function before touching its interpreter, and that each mediator function is currently a raw pass-through. That is what you harden across the lab.
Pass criteria
The DB is seeded, both servers answer on loopback, and a benign account question
drives a tenant-scoped db_lookup that returns the caller's own Globex row (no
other tenant, no side effect). That confirms the stack is live and normal use works.
listener.pymediator.pymetadata_stub.pyseed_db.pysinks.py