How do you stop a coding agent downloading and running malware?
Check each shell command and fetch before the coding agent runs it, and deny the patterns that turn a download into execution. In practice, that means three policies on the tool call. One refuses a download piped straight into a shell, one scores every host in the command against threat intelligence, and one refuses destructive or credential-reading commands in case something gets through.
A coding agent runs with the developer's permissions. When it runs curl and pipes the result into sh, the script runs as the developer, with access to their files, their SSH keys, and their cloud credentials. The check has to happen before the command runs, because nothing after that point can undo it.
Arcjet ships each of these as a starter policy and decides every shell command and fetch before it runs. One set of policies covers Claude Code, GitHub Copilot, Cursor, and OpenAI Codex, from the hooks that the agents already fire, with no code change. The host scoring, Arcjet destination threat analysis, also runs in custom agents, inside the tool before it contacts anything.
How does a coding agent end up running malware?
A coding agent rarely decides to run something malicious on its own. It follows instructions and examples from its context, and several common sources can carry a bad download:
- Install instructions. A README or a documentation page says to install a tool with
curl -fsSL https://example.com/install.sh | sh. The agent copies the pattern. - Poisoned URLs. A web page, an issue comment, or an MCP tool response names a download URL on a host the attacker controls. For more information about untrusted MCP servers, see what a rogue MCP server is and how to detect one.
- Injected instructions. Text planted in a file or page tells the agent to fetch and run a helper script as part of the task. For more information, see indirect prompt injection in agentic workflows.
A hook is a request that the coding agent sends to an external service, such as Arcjet, before it runs a tool, and the answer decides whether the tool runs. The poisoned content arrives as a tool result, after the tool has run, and coding agents don't give hooks a point to withhold it. Arcjet stops the next tool call instead, which is the one that downloads and runs the payload.
How do you block a download piped into a shell?
Deny a shell command that contains a pipe, a fetcher such as curl or wget, and a shell such as sh or bash. On a coding agent, Arcjet splits each shell command into command_tokens: the words of the command, split on whitespace and on the shell's chaining operators. The following policy is the coding-agent.piped-installer starter in the Arcjet Console:
package arcjet.guard
import rego.v1
deny contains "piped-installer" if { input.values.tool_kind == "shell" contains(input.values.command, "|") some fetcher in input.values.command_tokens lower(fetcher) in {"curl", "wget"} some shell in input.values.command_tokens lower(shell) in {"sh", "bash", "zsh", "dash"}}A plain download passes, and so does a download piped into a tool such as jq. The rule refuses only the combination that executes what was downloaded.
The rule compares whole tokens, so it matches curl but not a fetcher called by absolute path, such as /usr/bin/curl. It also doesn't see a download that is saved to a file and run in a later command. The destination threat starter in the next section covers both of those cases when the host scores high or critical.
How do you score the host in a shell command?
Score every host that the command would contact, whatever the command does with the response. On a coding agent, Arcjet fills an input named destinations with the hosts of a tool call's URL arguments, plus the hosts of absolute URLs inside a shell command. A policy that declares an Arcjet threat intelligence detector with the ID dest over destinations denies the call when any host scores high or critical risk. The following policy is the coding-agent.destination-threat starter:
deny contains "malicious-destination" if { input.signals.ip_threat.dest.risk_level in {"high", "critical"}}The rule doesn't check tool_kind, so a curl in a shell call is refused the same way as a web fetch. It also catches the cases the piped-installer rule misses: a download to a file, a fetcher called by absolute path, or a wget into a directory, as long as the URL is absolute and the host scores high or critical.
A domain allowlist, which permits only the hosts that it lists, doesn't cover this path when it's scoped to web tools. The coding-agent.egress-allowlist starter checks only calls where tool_kind is web, so a URL inside a shell command doesn't hit it. For more information about allowlists and how to combine one with threat scoring, see how to build an egress allowlist for AI agents. For the detector's fields and fail-closed behavior, see how to stop an AI agent contacting malicious URLs and domains.
Which other policies belong on the same tool call?
Arcjet runs every live policy whose Execute on setting is Tool call in one round trip, and applies the most restrictive decision. You keep each policy about one thing and still enforce them together. Evaluation runs at the edge in over 300 data centers, so the added latency stays small. The following starters are the ones most relevant to a malicious download:
| Starter | What it denies | Why it helps here |
|---|---|---|
coding-agent.piped-installer | A download piped into a shell | Refuses the most common download-and-run pattern |
coding-agent.destination-threat | A destination whose threat risk is high or critical | Refuses a download from a known-bad host by any tool |
coding-agent.destructive-command | rm, dd, mkfs, sudo, and similar commands in a shell call | Limits what a follow-up command can do |
coding-agent.credential-access | Tool calls and commands naming credential files | Refuses a command that reads |
coding-agent.protected-paths | Writes to CI configuration and version-control internals | Refuses a write to |
For more information about the command and credential policies, see how to block dangerous commands in coding agents and how to stop coding agents reading secrets.
Can a tool-call policy stop malicious packages?
Not on its own. A malicious package is a different problem, and it needs a different tool. A command such as npm install some-package or pip install some-package names no URL, so there is no host in the command to score. The package is fetched from the registry, which is a legitimate host with a clean reputation, even when the package itself isn't.
Arcjet controls the install command: a tool-call policy can refuse an install command outright, or refuse a package manager pointed at an unexpected registry URL. Whether a given package version contains malware is the job of software composition analysis and package scanning tools, so pair Arcjet with one. For more information about how malicious packages reach a project, see package hijacking and dependency confusion attacks.
Which controls pair with tool-call policies?
Tool-call policies depend on the hook firing and on the agent honoring the answer. Pair Arcjet with the following controls to cover the paths that sit outside it:
- A reliable hook path. Claude Code and Copilot HTTP hooks fail open on a timeout or network error, which means the call goes ahead. Arcjet keeps the added latency small by evaluating at the edge, and the Arcjet wrapper for Codex and Cursor fails closed, which means it denies the call. For more information, see coding agent hooks fail open.
- Codex hosted tools. OpenAI Codex runs hosted tools such as
WebSearchoutside the local hook path, so a tool-call policy never sees them. - An allowlist for unknown hosts. Threat scoring covers known-bad hosts. A host with no threat record scores risk
noneand is allowed. An allowlist catches the host that has no record. - Controls outside the agent. A hook covers the agent that it's installed in. A command that a developer runs in their own terminal, or in a tool with no hook, is outside the policy. Within the agents that it covers, Arcjet installs through managed settings or mobile device management (MDM), so a developer can't remove the hooks without administrator access.
- Package scanning for dependencies, as the preceding section describes.
Where does Arcjet fit against malware downloads?
With Arcjet, a download piped into a shell, a fetch from a known-bad host, and a destructive or credential-reading follow-up are denied before they run, whichever coding agent your developers use. The following capabilities apply:
- One set of policies across agents. Arcjet decides each shell command and fetch before it runs, on Claude Code, Copilot, Cursor, and Codex, with no per-vendor rules.
- Single-concern starters enforced together. Each starter covers one pattern, and Arcjet applies the most restrictive decision across all of them on every tool call.
- Destination threat analysis everywhere. Arcjet threat intelligence scores the hosts in shell commands and fetches, and the same analysis runs inside the tools of custom agents.
- A record of every decision. Every session and decision is in the Arcjet Console, and decisions export to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan).
To start, publish the starters in dry run. A dry-run rule records what it would refuse without blocking anything, and Arcjet starts every new rule that way. Read the commands that each rule would have refused in the Activity view of the Arcjet Console, and then set the rule live. For vendor-specific setup, see the guides to securing Claude Code in the enterprise, GitHub Copilot's agent, Cursor's agent, and OpenAI Codex.
Learn more: Coding agent policies · Destination threat detection · Secure coding agents
Frequently asked questions
How do I stop Claude Code or Copilot from running curl | sh?
Add a policy that runs on the agent's hook before each tool call and denies a shell command containing a pipe, a fetcher such as curl or wget, and a shell such as sh or bash. A hook is a request that the agent sends to an external service before it runs a tool. Arcjet ships this policy as the coding-agent.piped-installer starter.
Does a domain allowlist stop a coding agent downloading from a bad host with curl?
Not if the allowlist checks only web tools, because a URL inside a shell command isn't a web tool call. Arcjet destination threat scoring reads the hosts of absolute URLs inside shell commands, so it covers that path.
Does Arcjet scan npm or PyPI packages for malware?
No. Package scanning is a separate tool category. A command such as npm install names no URL, so there is no host to score, and the registry itself is a legitimate host. Pair Arcjet with a software composition analysis or package scanning tool.
Can a hook stop a poisoned web page from reaching the agent?
No. The tool has already run when its result comes back, and no supported coding agent lets a hook withhold it. Arcjet denies the next tool call instead, which is the one that downloads and runs the payload.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Publish the piped-installer and destination-threat starters in dry run to see which downloads your coding agents would have been refused.