Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B3.11.mp4The IDE agent holds git credentials, cloud credentials and a shell, in an unmanaged environment.
The strongest containment a developer does not notice: credential deny-lists and workspace confinement first.
Measure the default agent's blast radius and reachable credentials, then rank controls by friction.
The coding agent on an engineer's laptop holds repository write access, a cloud credential, and whatever MCP servers were convenient. It is the highest-privilege agent in most organisations and the least governed.
cybertravels/devagent.py is the containment for the one in Alex's IDE: a credential deny-list matched against resolved paths, workspace confinement that survives a symlink, and a redacted environment — ordered by the friction a developer feels, because the containment they notice is the one they switch off.Containment for the coding agent in the developer's own IDE, which has none of the controls it helped build and holds git credentials, cloud credentials and a shell. Ordered by the friction a developer feels — credential deny-list, then workspace confinement, then command review — because the guard they notice is the one that gets switched off, and a configuration with everything disabled still reports as compliant.
The IDE agent holds git credentials, cloud credentials and a shell, in an environment nobody manages.
Credential deny-lists and workspace confinement first — the strongest containment a developer does not notice.
Developer agents with confinement applied, and the credentials still reachable from inside the workspace.
Measure this
| what to measure | denominator or target |
|---|---|
| developer agents with confinement applied | share of developer agents |
| credentials still reachable from inside the workspace | count; target zero |
the highest-privilege agent in most organisations
+--------------------------------------------+
| coding agent on an engineer's laptop |
| repo:write . cloud creds . shell . MCP |
+--------------------------------------------+
| | |
no SSO no egress no telemetry
policy
governed by: whatever the engineer clickedThe coding agent in a developer's IDE is the most privileged agent in most organisations and the least governed. It sits upstream of everything this track has built: it writes the code the pipeline later analyses.
What it holds by default:
~/.aws or ~/.config/gcloud,That is a production identity in an unmanaged environment, driven by a model reading code from the internet.
The binding constraint here is not technical feasibility — it is developer tolerance. A containment scheme that adds friction to the inner loop is disabled within a week, and a disabled control protects nothing. So the design goal is the strongest containment a developer does not notice.
| control | friction | what developers actually experience |
|---|---|---|
| deny-list credential paths | 0.0 | the agent cannot read ~/.aws, ~/.ssh, ~/.env. Developers never noticed. |
| workspace confinement | 0.1 | the agent sees the open repo only. Occasionally annoying for monorepo hops. |
| egress allowlist | 0.2 | package registries and your VCS. Breaks the odd curl in a generated script. |
gate git_push | 0.4 | one confirmation before code leaves the machine. Noticed, usually accepted. |
| gate every write | 0.9 | confirmation per file write. Abandoned within a week, every time. |
| no shell at all | 1.0 | removes the inner loop. Nobody will use the agent. |
Everything in this lesson is a review someone has to remember to do. Written as a skill, it is a review that fires whenever an agent opens a repository — including this one.
The skill's central instruction is easy to miss and decides the outcome: rate an injection finding by what the allowlist permits, not by the text of the injection. The payload is the attacker's choice and costs nothing to change; the allowlist is yours.
The skill — skills/appsec/coding-agent-hardening/SKILL.md
name: coding-agent-hardening
description: >-
Review the configuration of a coding agent — its skills, tools, hooks, MCP
servers and autorun rules — for ways a repository can turn the agent against
its operator. Use when asked to audit agent configuration, review a SKILL.md
or AGENTS.md, assess MCP server risk, check tool allowlists, or secure
developer AI tooling.
allowed-tools: Read, Grep, GlobA coding agent reads the repository it is working in. That makes every file in the repository an input to a system that can run commands — and the agent's own configuration is the control surface.
The threat is not that the agent is malicious. It is that instructions in a repository are indistinguishable, at the token level, from instructions from the operator.
Auditing .claude/, AGENTS.md, .github/, MCP configuration, CI that invokes an agent, or any repository that a coding agent will open.
1 — Inventory the surface. List every file that reaches the agent's context automatically: skill files, agent instruction files, hooks, MCP server definitions, settings with tool allowlists, and any file the agent is told to read on startup. Record which are operator-controlled (a maintainer wrote them) and which are content (a contributor, a dependency, or a fetched document can influence them).
2 — Find the confusion. For every content-controlled input, ask: if this file contained an instruction, would the agent follow it? Grade each:
| Grade | Meaning |
|---|---|
isolated | content is quoted as data and never read as instruction |
advisory | content can influence phrasing but not tool calls |
directive | content can cause a tool call |
Any directive path from unreviewed content is a finding, and its severity is whatever the agent's most powerful allowed tool can do.
3 — Check the allowlist against the blast radius. For each pre-approved tool, state the worst outcome of one call with attacker-chosen arguments. A pre-approved Bash with unrestricted arguments is equivalent to pre-approving everything else; note it as such rather than listing it as one item.
4 — Check the hooks. Hooks run without the model's judgement. A hook that executes repository-supplied code (a script path from a config file, a test command from package.json) runs attacker-supplied code the moment the agent opens the repository. This is the highest-value finding in most reviews.
5 — Check the MCP servers. For each: who operates it, what it can reach, whether its tool descriptions are trusted input (they are model-visible text from a third party), and whether its credentials exceed the task.
6 — Check the escape hatches. Can a human stop it? Is there an audit trail that survives the agent's own actions? An agent that can edit its own logs has no logs.
Input — the fixture committed at the top of scripts/coding_agent_hardening.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
tool writes scope reversible inner loop?
----------------------------------------------------------
read_file False self True True
write_file True project True True
run_tests True self True True
run_shell True tenant False True
git_commit True project True True
git_push True project False False
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"surface": [{"path": "str", "control": "operator|content", "auto_loaded": true}],
"findings": [
{"kind": "prompt_injection|overbroad_tool|unsafe_hook|mcp_trust|no_audit",
"path": "str", "grade": "isolated|advisory|directive",
"worst_case": "str", "severity": "critical|high|medium|low",
"fix": "str"}
],
"allowlist_review": [{"tool": "str", "worst_single_call": "str", "bounded": true}]
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons B3.11Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b3-11-securing-the-developers-coding-agents:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B3.11 | type /b3-11-securing-the-developers-coding-agents |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B3.11 | type /b3-11-securing-the-developers-coding-agents in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B3.11 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B3.11 | type $b3-11-securing-the-developers-coding-agents, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py B3.11Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at B3.11 --out work/cybertravels
python3 scripts/checkpoint.py --at B3.11 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
The default developer agent scores a blast radius of 43 and can reach all seven paths including AWS, SSH and gcloud credentials. Containment reduces reachable paths to one source file with zero credentials reachable, and gating git_push drops the blast radius to 37 for 0.4 friction. The three lowest-friction controls remove every credential path without touching the inner loop.
Ship the credential deny-list first — it is a config file, it takes an afternoon, and no developer will notice. Then find out how many agents in your organisation could read ~/.aws/credentials yesterday.
What you can do now. Layers now stand between a compromised agent and a consequence — the policy decision at the tool call, the sandbox, egress, the budget, the gateway — plus the three the incident register adds: shared infrastructure that is no longer a channel, exemptions that cost blast radius, and an agent that has somewhere to report.
What you still cannot do. You have a secured architecture and nothing that builds on it. Every control here is stated as a rule; none of it is a pipeline anyone operates, and the first agentic system most organisations run is a security tool that reads untrusted code all day.
Function C builds that system as an SDLC, and holds it to every rule in this chapter. Next → C2.0, what an AI SDLC means.
Tools used
Skill source: skills/appsec/coding-agent-hardening