All lessons › Function B — Securing AI Architectures › Securing the Architecture — Runtime and the Gateway
B3.11

Securing the developers' coding agents

B3
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/B3.11.mp4
Risk

The IDE agent holds git credentials, cloud credentials and a shell, in an unmanaged environment.

Control

The strongest containment a developer does not notice: credential deny-lists and workspace confinement first.

The lab

Measure the default agent's blast radius and reachable credentials, then rank controls by friction.

◉Use case relevance

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.

At CyberTravels. The agent that wrote all of the above has none of it. 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.

◴What this lesson is — Day 0, Day 1, Day 2

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.

Day 0 — why

The IDE agent holds git credentials, cloud credentials and a shell, in an environment nobody manages.

Day 1 — how

Credential deny-lists and workspace confinement first — the strongest containment a developer does not notice.

Day 2 — measure

Developer agents with confinement applied, and the credentials still reachable from inside the workspace.

Measure this

what to measuredenominator or target
developer agents with confinement appliedshare of developer agents
credentials still reachable from inside the workspacecount; target zero

▦The framework, and how it works

   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 clicked

The 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:

  • the developer's git credentials — push access to everything they can push to,
  • the whole monorepo on disk, including files they never open,
  • a shell, usually unrestricted,
  • their cloud credentials in ~/.aws or ~/.config/gcloud,
  • whatever MCP servers they connected, each with its own authority.

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.

2 · Demo — measure the default configuration

3 · The control — rank by friction, ship the invisible ones first

controlfrictionwhat developers actually experience
deny-list credential paths0.0the agent cannot read ~/.aws, ~/.ssh, ~/.env. Developers never noticed.
workspace confinement0.1the agent sees the open repo only. Occasionally annoying for monorepo hops.
egress allowlist0.2package registries and your VCS. Breaks the odd curl in a generated script.
gate git_push0.4one confirmation before code leaves the machine. Noticed, usually accepted.
gate every write0.9confirmation per file write. Abandoned within a week, every time.
no shell at all1.0removes the inner loop. Nobody will use the agent.
The first four ship today and cost nothing anyone will complain about. The last two are the ones people propose in meetings, and they are uninstalled within the week — a control that gets turned off is worth less than a weaker one that stays on.

4 · The audit, as a skill an agent runs on itself

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.

▶Real time execution as skill

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, Glob

Securing the developers' coding agents

A 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.

When to use this

Auditing .claude/, AGENTS.md, .github/, MCP configuration, CI that invokes an agent, or any repository that a coding agent will open.

Procedure

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:

GradeMeaning
isolatedcontent is quoted as data and never read as instruction
advisorycontent can influence phrasing but not tool calls
directivecontent 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.

Example

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.

Output contract

{
  "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}]
}

Failure modes

  • Reviewing the agent's instructions and not its inputs. The instructions are the part you control; the inputs are the part an attacker controls.
  • Treating tool descriptions as trusted. They are third-party text placed directly in the model's context.
  • Assuming a human reviews every action. Check whether the mode in use actually prompts, and audit the configuration that decides that.
  • Rating an injection finding by the text of the injection. Rate it by what the allowlist permits.

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.

1In your AI agent — you are taught

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.11

Then 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:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons B3.11type /b3-11-securing-the-developers-coding-agents
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons B3.11type /b3-11-securing-the-developers-coding-agents in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons B3.11type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons B3.11type $b3-11-securing-the-developers-coding-agents, or run /skills

2As one Python command — you just run it

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.11

3Read the code, and the difference — nothing runs

Neither 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 --diff

On 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.

✓What you just proved

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.

✎Your turn

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.

→Where this leaves you

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

DockerCiliumGLM-4.6Claude Haiku 4.5

Skill source: skills/appsec/coding-agent-hardening

← PreviousB3.10 · The agent's escalation path