SSD Nodes Learn 🎉 VPS from $5.50/mo
How to do am Matt ConnorBy Matt Connor

Prompt Injection Against Coding Agents: Wetin Fit Happen

Coding agent fit follow attacker text from files, PRs, web pages, or tool results. See the real server attack surfaces and defences wey reduce damage.

Wetin prompt injection against a coding agent be

Prompt injection against a coding agent easy to explain: text wey the agent read fit turn instruction wey the agent go follow. The agent fit open file, pull request comment, web page, or result from tool call. All of dem enter as the same kind text like your own request. If attacker control any of that text, the attacker dey write inside your session.

Every current agent product get this property. The model receive one sequence of tokens. Your request, system prompt, file contents, and tool results join together, then the model predict wetin go come next. No privilege bit dey on any token. Nothing for the format show which part you authorise and which part come from stranger README file.

This page na the threat model: where attacker-controlled text fit reach agent wey dey run for server, wetin attacker fit gain for each point, and which defences worth the effort. The containment advice for our other guides only make sense after you understand wetin you dey contain the agent from.

Why model no fit separate content from instructions

Training dey help, but e no solve the matter. Current models dey trained to treat retrieved text with suspicion, and dem dey refuse plenty crude attempts. Refusal na probability, no be rule. Attacker fit rephrase am, retry, and hide the text inside format wey nobody plan for, and nothing limit how many wordings dem fit try.

The OWASP GenAI project dey track this as LLM01:2025 Prompt Injection and e divide am into two. Direct injection na when user own prompt change model behaviour. Indirect injection na when external content, like website or file, change behaviour as model dey process am. Indirect injection na the one wey matter for server, because agent dey read far more text than wetin you type.

The first systematic study na Greshake and colleagues, Not what you've signed up for (2023). Their conclusion na the sentence wey you suppose keep: when application feed retrieved text into model wey fit call tools, processing that text dey close to arbitrary code execution.

Condition wey turn a read into breach

Reading hostile text no be damage by itself. The damage need one way to comot from the machine.

Simon Willison name this combination the lethal trifecta for June 2025. Agent wey get private data, dey exposed to untrusted content, and fit send data outside fit make person convince am to move the first one go through the third one.

Coding agent wey dey your VPS get all these things from day one. The private data na your source code, your .env file, your SSH keys, and your shell history. The untrusted content na every repository, page, and tool result wey e read. The way out fit be git push, curl, npm publish, pull request body, or link wey e print for your terminal and you click.

You no fit remove the second condition, because reading untrusted text na the work wey you hire the agent to do. So every practical defence dey work on the other two.

Where untrusted text dey reach coding agent for server

The repository wey agent dey work inside

Every file for checkout na input. Source comments, README.md, changelogs, test fixtures, vendored code, and the agent instruction files themselves: CLAUDE.md, AGENTS.md and their equivalents. Agent wey you ask to understand codebase go read dem, because na wetin you ask am make e do.

Wetin attacker gain here na access to everybody wey clone repository and point agent go am. Instruction files na the direct route pass, because dem dey exist so agent go read dem as instructions. Pull request wey add four useful lines to CLAUDE.md and one line wey redirect agent fit pass human reviewer eye when e dey skim.

Issues, pull requests and code review comments

Anything wey stranger fit type for your tracker go reach agent immediately you ask am to triage. For May 2025, Invariant Labs publish one GitHub MCP finding wey get exactly this pattern. Developer agent get access to one public repository and private ones. Attacker file issue for public repository. When developer ask agent to check open issues, agent read contents from private repository and write dem inside pull request for public side.

That report describe architectural problem, no be code defect for MCP server. Agent hold one wide access token, read from public inbox, and get permission to write. Nothing misconfigure for the usual way, na why answer na scoping, no be patching.

Web pages wey agent dey fetch

Documentation, forum answers, vendor page, search result. Any of dem fit carry text wey attacker write for agent instead of for you. HTML wey convert to text give attacker more space, because content wey browser no dey display still go reach model.

Attacker gain control for the moment wey person dey watch agent least. Nobody dey read complete text of page wey agent fetch while e dey search for information.

MCP tool output

MCP (model context protocol) na the common way agents dey connect to external tools. Results dey come back as text and go straight into context window. Two surfaces dey here, no be one. The data wey tool return na the obvious one. The tool own name and description, wey model dey read to decide when to call am, na the other one. Server wey you no control fit change either one between calls.

Attacker wey put text inside output from one tool fit reach every other tool wey agent get. Na so injection for something wey no too valuable fit end up driving something wey get high value.

CI logs, build output and dependency metadata

npm install dey print text from packages wey you no write. Test failure dey print assertion message from library. Continuous integration (CI) job log fit get thousands of lines of third-party output. Ask agent to fix failing build and e go read everything.

Here attacker gain access to build machine. Build machine normally hold deploy credentials and registry tokens, and people dey pay less attention to am than laptop.

Wetin attacker fit actually collect

Four outcomes dey worth planning for.

Credential theft. Anything wey the agent process fit read dey included: environment variables, ~/.aws/credentials, ~/.ssh, a gh token, or a Docker config file. Sending dem outside no need curl. Commit to a branch, pull request description, package wey dem publish to a registry, or DNS lookup for a name wey attacker control all fit move data comot from the box.

Code changes wey you approve. Writing code na wetin the agent dey do, so making am write one line wey subtly wrong na the cheapest outcome to reach. E fit add dependency, or add logging call wey carry token enter log wey you ship go somewhere else.

Persistence. File wey dem write once fit continue to work without model involvement: hook for .git/hooks, postinstall script for package.json, line wey dem append to shell startup file, or extra line for CLAUDE.md. The next command go run am.

Movement inside your network. The agent dey run where you put am. If that box fit reach database for loopback, internal admin service, your cloud provider's metadata service, or another host for the private network, anything wey dey drive the agent fit reach dem too.

Auto-approve modes dey remove the final check

For default mode, Claude Code dey ask before e run command or edit file. That prompt na the human check wey dey between every surface above and real action. Modes wey remove the prompt dey remove that check.

The documentation talk clearly about bypassPermissions: use am only for isolated environments like containers or VMs where Claude Code no fit cause damage. Auto mode softer, and e auto-approve tool calls with background safety checks wey verify say actions match wetin you request. Those checks dey catch plenty things. But dem still na model judgement about model output, so treat dem as filter, no be boundary.

Administrator fit remove both. Set permissions.disableBypassPermissionsMode or permissions.disableAutoMode to "disable" for settings file, then put that file for managed settings so checked-out project no fit override am. Our guide about Claude Code auto mode and permission rules explain where each rule dey take effect.

Defences, wey rank based on wetin dem fit buy you

None of these na complete fix. Each one either reduce wetin the agent dey hold, or reduce wetin e fit do with am.

  1. Machine wey you fit destroy and rebuild, so compromise go cost you one hour instead of incident.
  2. Credentials wey separate from your own, limited to one repository, and short lived.
  3. No long-lived secrets for the environment wey agent commands inherit.
  4. Operating system enforcement for network egress and file access, wey apply to every process wey agent start.
  5. Approval prompts wey remain on for writes and network calls.
  6. Hooks as deterministic backstop for specific actions wey you fit name.
  7. Read the diff before you merge am.

The order matter. Items 1 through 4 still hold even when attacker fully control the model. Items 5 through 7 depend on human attention, and na exactly this attention dey stop during long agent run.

Put the agent for machine wey you fit throw away

A VPS wey hold checkout and one scoped token na much smaller prize than laptop wey get your keys. Run the agent as its own unprivileged user, no be as your login account and no be as root. Our guides on disposable VM for coding agents and least-privilege users for VPS explain the setup, while how to run Claude Code safely for VPS explain the daily arrangement.

Remove secrets from the environment

Every child process fit read environment variable, which mean every command wey agent run fit read am too. Claude Code sandbox fit unset named variables before each sandboxed command. For Linux, sandbox need two packages first:

sudo apt-get install bubblewrap socat

Then for ~/.claude/settings.json:

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

A deny entry dey unset that variable before each sandboxed command run, while allowedDomains hold sandboxed commands to the hosts wey you list. The credentials block need Claude Code v2.1.187 or later, checked August 2026. Run /sandbox inside a session to see which layers dey active and which dependencies dey missing. Deciding which secrets need to exist for that box at all na the bigger half of the work, and keeping secrets out of an AI agent's reach dey explain how to handle am.

Cut network egress for operating system level

Firewall rule no care wetin model decide. Run the agent as dedicated agent user, then drop wetin that user send:

table inet agentcage {
  chain output {
    type filter hook output priority filter; policy accept;
    meta skuid "agent" ct state established,related accept
    meta skuid "agent" oif lo accept
    meta skuid "agent" counter drop
  }
}

This one leave agent user with loopback only, so its traffic must pass through proxy wey you run for the same box, while proxy hold hostname allowlist. With https_proxy pointing to that proxy, client send CONNECT request and proxy perform name lookup, so agent no need its own outbound DNS (domain name system). Check your work with sudo nft list ruleset and watch the counter for drop rule rise while agent dey try reach something new.

Keep second SSH session open while you apply firewall changes. Check wetin your container runtime dey do to these rules too: Docker writes its own chains, and published Docker ports bypass ufw describe the surprise wey this one cause.

Hooks: the check wey model no fit talk its way past

Claude Code enforce permission rules and hooks, no be the model. The documentation talk am clearly: instructions for your prompt or CLAUDE.md shape wetin Claude try do, but dem no change wetin Claude Code allow. That difference na the main value. A line inside CLAUDE.md wey read "never run curl" na suggestion wey injected paragraph fit argue against. Hook na process wey return exit code.

Register a PreToolUse hook inside .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
          }
        ]
      }
    ]
  }
}

The hook receive tool call as JSON through standard input. Exit code 2 block the call and show Claude the reason from standard error. Exit code 0 allow the call continue through normal permission flow.

#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
  echo "Blocked: this repository does not allow outbound network commands." >&2
  exit 2
fi
exit 0

Now make we talk the honest part. This na denylist over shell string, and denylists over shell strings get loopholes. python3 -c fit open socket without using the word curl. A make deploy target fit hide the same call one level further down. Write hooks for mistakes wey you fit name, then put the boundary wey you actually rely on inside kernel or on the network.

Permission deny rules get one matching limit wey you need know. Read and Edit deny rules cover Claude own file tools and file commands wey e recognise inside Bash, such as cat, head, tail and sed. Dem no cover Python or Node script wey open the file by itself. Rules dey evaluate deny first, then ask, then allow, so deny rule no fit carry allowlist exception.

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(./secrets/**)",
      "Bash(git push *)"
    ]
  }
}

Monitor wetin dey leave, and read the diff

Agent run produce diff and set of network calls. Both deserve review before anything merge or deploy. A self-hosted security review pass over the diff fit catch different kind change from human skim, while knowing wetin coding agent dey send outside the box tell you wetin normal traffic look like, so odd request go stand out.

Wetin still never solve

Today, no reliable separation dey between content and instruction. Every defence wey release na either filter wey fit fail or limit on wetin fit happen after failure. Nothing for the stack dey mark one span of text as data wey dem must never obey.

Filters dey help, and dem dey fail too. Classifier wey catch most injection attempts still need get am right every time, but attacker only need get am right once. Na this difference make published success rate for one defence be starting point for the next attempt, instead of guarantee.

The most promising work dey for design level, not model level. CaMeL, from Defeating Prompt Injections by Design (Debenedetti and colleagues, 2025), first extracts control flow and data flow from the trusted request, so untrusted data no fit change wetin the program dey do. Then e enforces capability checks when tools dey called. The paper's own figures for the AgentDojo benchmark show the cost of this approach.

ChartAgentDojo tasks solved, published figures from the CaMeL paper (2025)
The data behind this chart
[
  {
    "label": "Undefended agent",
    "tasks_solved_pct": 84
  },
  {
    "label": "CaMeL",
    "tasks_solved_pct": 77
  }
]

The undefended agent solve 84 percent of tasks. CaMeL solve 77 percent with security guarantee attached. Those na the figures wey the paper publish for one benchmark, and dem no be measurement of your workload. The gap between dem na roughly wetin real guarantee dey cost today.

Until design like that ships inside the tools wey you dey use every day, plan as if the agent go get breached at some point, and make that event boring. Na the complete reason for using disposable machine, scoped credentials, controlled egress, and habit of reading the diff.

FAQ

I fit stop prompt injection by telling the agent make e ignore instructions for files?

No. That sentence na text for the same context window wey the attack dey, and e dey compete with the attacker's text on equal terms. Claude Code documentation make the line clear: instructions for your prompt or CLAUDE.md dey shape wetin the agent try do, and dem no change wetin the tool allow. Treat instruction file as statement of intent, and put anything wey you rely on inside permission rules, a PreToolUse hook, or firewall rule.

Prompt injection na real risk if na only my own repository the agent dey touch?

Yes, because your repository full of text wey you no write. Dependency README files, lockfile URLs, test fixtures, vendored code, and the output of npm install all dey enter during normal task. Anything wey come from issue tracker or documentation site dey arrive the same way. The risk dey increase based on how much the agent read, and useful agent dey read plenty.

Container fit solve this if I run the agent inside one?

E fit limit the damage, but only if you remove the credentials too. Container wey get your SSH agent forwarded, cloud credentials for the environment, and unrestricted network access dey hand attacker almost everything wey the host get. The real thing wey container give you na filesystem wey you fit delete, plus clean place to enforce egress rules. Join am with token wey scope to one repository.

Which one change go reduce the risk pass?

Remove long-lived credentials from the environment wey the agent commands inherit, then give that machine default-deny egress policy. Together, dem break the third condition for the lethal trifecta: text still fit hijack the agent, but the data wey e reach no get useful place to go. Approval prompts and diff review still help, and dem depend on human wey remain alert through long run. Na why dem rank below those two changes.