Audit the agent's code · framework appsec
The companion to redteam-an-agent. That skill breaks the running
agent through the model. This one breaks it through its own source code — the classic appsec
layer the model's judgment never touches.
Written from real engagements: two well-defended agents whose LLM refused every prompt-injection
attempt were broken here instead — one by an unauthenticated path traversal in a channel's
file handler, one by a fail-open sandbox. The model can't defend a code path it never sees.
Why this layer
Flagship agents increasingly harden the LLM layer: they refuse injected commands, resist
memory poisoning, even decode obfuscated payloads to inspect them. Good. But the code around the
model — the channel that parses an inbound message, the handler that saves an attachment, the loader
that reads config/skills, the toggle that turns the sandbox on — is ordinary software, and it is
frequently not hardened. An attacker who reaches those paths bypasses the model entirely. Audit
them like you'd audit any web app.
Authorization & safety
Follow every rule in ../README.md: authorized target, disposable environment,
harmless proof, responsible disclosure. Reading source is safe; never run untrusted target code
on a machine you care about.
The untrusted-input boundaries (where to look)
Trace every place external, attacker-influenced data enters, and follow it to a dangerous sink:
- Channel / message handlers — attachment
filename → file write (path traversal); sender_id
→ path; message body → any sink. These run on every inbound message, often before auth.
- File / media / download tools — path built from untrusted input; write/read outside the
intended dir; symlink following.
- Config / skill / MCP / plugin loaders — untrusted content → deserialization (
pickle,
yaml.load, eval), dynamic import, or a path.
- The security controls themselves — the sandbox toggle, the guard, the auth gate. Audit the
control, not just the happy path.
Sinks by language
- Python:
os.system, subprocess(..., shell=True), Path(x) / untrusted, open(untrusted,'w'),
pickle.load, yaml.load (non-safe), eval/exec.
- JS/TS:
child_process / execSync, eval, new Function, fs.write* with an untrusted path.
- Rust:
Command::new("sh").arg("-c") with interpolated input, fs::write/File::create on a
path joined from untrusted input without canonicalize + containment check.
Four techniques that actually land
- Cross-component consistency diff. When a codebase has N similar handlers (channels, parsers,
loaders), check whether they all apply the same safety helper. The one that forgot is the bug.
(Real case: every channel ran the attachment filename through a sanitizer — except one, which
wrote
dir / raw_filename. That one had a path traversal.)
- Auth ordering. Find dangerous operations (file write, download, exec) that run during message
parsing — before the pairing/auth gate fires. If the write happens first, it's unauthenticated,
and the agent's access control (however strong) doesn't cover it.
- Fail-open controls. A security control that, on a missing dependency or an error, degrades
to insecure and keeps running instead of failing closed. (Real case: a tool sandbox that
silently disabled itself when Docker was absent and ran tools unsandboxed on the host — the
promised isolation quietly wasn't there.)
- Trust-boundary laundering. Data that's untrusted as content but trusted once it's a config
value, a stored skill, or a peer-agent message — where the same string gets more authority by
changing container.
Rigor — the same bar as everything we do
- Attacker-reachability. A sink is only a finding if untrusted, attacker-controlled input
reaches it. Trace the source; if you had to set the state up yourself, it's not a vulnerability.
- Don't overclaim "systemic." Check each instance. (Real case: the traversal looked like it hit
five channels; on inspection four sanitized correctly — only one was vulnerable. Report the one.)
- Confirmed-in-code vs live-fired. Say which. A traced source→sink with no sanitizer is a
code-confirmed finding; only claim "exploited" if you actually fired it end to end.
- Impact, concretely. Arbitrary file write → overwrite the config to redirect the model to an
attacker endpoint (MITM every prompt + steal the key), or plant a startup file → RCE. Spell out
the chain; don't stop at "write primitive."
Report
Per finding: the untrusted source, the sink, the missing check, whether it's
code-confirmed or live-fired, the reachability conditions (which channel/mode enabled), the
concrete impact chain, and the fix (the exact sanitizer/ordering/fail-closed change).
Disclose to help defenders — the finding and fix, not a weaponized payload; pick your disclosure model deliberately.
Related
Behavioral / LLM-layer red-team: redteam-an-agent. Supply chain of
skills/MCP: rt7-supply-chain.
1---2name: audit-agent-code3description: Security-audit an AI agent's OWN framework code for classic appsec vulnerabilities the LLM can't defend — path traversal, command injection, SSRF, fail-open security controls, missing/late auth, unsafe deserialization — especially at the untrusted-input boundaries (channels, file/media handlers, config & skill loaders) that run around and before the model. The LLM layer is often hardened; the plumbing around it usually isn't. Authorized testing of agents you own or are permitted to test.4license: Apache-2.05---67# Audit the agent's code · framework appsec89The companion to [`redteam-an-agent`](../redteam-an-agent/SKILL.md). That skill breaks the *running*10agent through the model. This one breaks it through **its own source code** — the classic appsec11layer the model's judgment never touches.1213> Written from real engagements: two well-defended agents whose LLM refused every prompt-injection14> attempt were broken here instead — one by an unauthenticated **path traversal** in a channel's15> file handler, one by a **fail-open sandbox**. The model can't defend a code path it never sees.1617## Why this layer18Flagship agents increasingly harden the **LLM layer**: they refuse injected commands, resist19memory poisoning, even decode obfuscated payloads to inspect them. Good. But the code **around** the20model — the channel that parses an inbound message, the handler that saves an attachment, the loader21that reads config/skills, the toggle that turns the sandbox on — is **ordinary software**, and it is22frequently *not* hardened. An attacker who reaches those paths bypasses the model entirely. Audit23them like you'd audit any web app.2425## Authorization & safety26Follow every rule in [`../README.md`](../README.md): authorized target, disposable environment,27harmless proof, responsible disclosure. Reading source is safe; **never run untrusted target code**28on a machine you care about.2930## The untrusted-input boundaries (where to look)31Trace every place external, attacker-influenced data enters, and follow it to a dangerous sink:32- **Channel / message handlers** — attachment `filename` → file write (path traversal); `sender_id`33 → path; message body → any sink. These run on *every inbound message*, often before auth.34- **File / media / download tools** — path built from untrusted input; write/read outside the35 intended dir; symlink following.36- **Config / skill / MCP / plugin loaders** — untrusted content → deserialization (`pickle`,37 `yaml.load`, `eval`), dynamic import, or a path.38- **The security controls themselves** — the sandbox toggle, the guard, the auth gate. Audit the39 control, not just the happy path.4041## Sinks by language42- **Python**: `os.system`, `subprocess(..., shell=True)`, `Path(x) / untrusted`, `open(untrusted,'w')`,43 `pickle.load`, `yaml.load` (non-safe), `eval`/`exec`.44- **JS/TS**: `child_process` / `execSync`, `eval`, `new Function`, `fs.write*` with an untrusted path.45- **Rust**: `Command::new("sh").arg("-c")` with interpolated input, `fs::write`/`File::create` on a46 path joined from untrusted input without `canonicalize` + containment check.4748## Four techniques that actually land491. **Cross-component consistency diff.** When a codebase has N similar handlers (channels, parsers,50 loaders), check whether they *all* apply the same safety helper. The one that forgot is the bug.51 *(Real case: every channel ran the attachment filename through a sanitizer — except one, which52 wrote `dir / raw_filename`. That one had a path traversal.)*532. **Auth ordering.** Find dangerous operations (file write, download, exec) that run during message54 *parsing* — before the pairing/auth gate fires. If the write happens first, it's **unauthenticated**,55 and the agent's access control (however strong) doesn't cover it.563. **Fail-open controls.** A security control that, on a missing dependency or an error, **degrades57 to insecure and keeps running** instead of failing closed. *(Real case: a tool sandbox that58 silently disabled itself when Docker was absent and ran tools unsandboxed on the host — the59 promised isolation quietly wasn't there.)*604. **Trust-boundary laundering.** Data that's untrusted as *content* but trusted once it's a config61 value, a stored skill, or a peer-agent message — where the same string gets more authority by62 changing container.6364## Rigor — the same bar as everything we do65- **Attacker-reachability.** A sink is only a finding if *untrusted, attacker-controlled* input66 reaches it. Trace the source; if you had to set the state up yourself, it's not a vulnerability.67- **Don't overclaim "systemic."** Check each instance. *(Real case: the traversal looked like it hit68 five channels; on inspection four sanitized correctly — only one was vulnerable. Report the one.)*69- **Confirmed-in-code vs live-fired.** Say which. A traced source→sink with no sanitizer is a70 code-confirmed finding; only claim "exploited" if you actually fired it end to end.71- **Impact, concretely.** Arbitrary file write → overwrite the config to redirect the model to an72 attacker endpoint (MITM every prompt + steal the key), or plant a startup file → RCE. Spell out73 the chain; don't stop at "write primitive."7475## Report76Per finding: the untrusted **source**, the **sink**, the **missing check**, whether it's77code-confirmed or live-fired, the **reachability conditions** (which channel/mode enabled), the78concrete **impact chain**, and the **fix** (the exact sanitizer/ordering/fail-closed change).79Disclose to help defenders — the finding and fix, not a weaponized payload; pick your disclosure model deliberately.8081## Related82Behavioral / LLM-layer red-team: [`redteam-an-agent`](../redteam-an-agent/SKILL.md). Supply chain of83skills/MCP: [`rt7-supply-chain`](../rt7-supply-chain/SKILL.md).