Egress allowlisting for coding agents and CI runners: the control that would have stopped the axios callback
Restrict outbound network access for coding agents and CI runners to a short list of known destinations. Most malware that reaches developers through packages, skills or MCP servers needs a second connection: to fetch its payload or to send stolen data out. In the 31 March 2026 axios compromise, StepSecurity's runner monitor flagged exactly that connection during a routine CI run, and the company says its egress allowlist would have blocked the payload fetch before the remote access trojan arrived (StepSecurity). Run the allowlist in audit mode first, then block. The cost is maintaining the destination list.
Outcome, time and access
Outcome: CI jobs and agent shell sessions can reach only the hosts they need. Anything else is logged, then blocked.
Time: about an hour to start audit mode on one repository's CI and one agent profile. Two to four weeks of observation before blocking.
Access needed:
- Admin on the GitHub repositories whose workflows you will change.
- Rights to deploy Claude Code managed settings, or each developer's agreement to edit their user settings.
- A named owner for the destination list. Without one, do not start.
Use this runbook if, and do not use it if
Use it if your CI runners or agents run with open internet access, which is the default for both.
Use it if you already block known-bad packages and want a layer for what the list has not caught yet.
Do not use it as your only control. The vendor evidence here comes from a company that sells egress control. Treat it as one layer.
Do not block on day one. Every build has destinations nobody wrote down.
The evidence, in order
StepSecurity's timeline, in UTC:
[email protected], carrying the payload, was published at 23:59 on 30 March 2026.[email protected]was published from a compromised maintainer account at 00:21 on 31 March.[email protected]followed at 01:00. Both added[email protected]as a dependency.- npm unpublished both axios versions at about 03:15.
During that window, Harden-Runner flagged an outbound connection to the attacker's server on port 8000 during a routine CI run in the Backstage repository. StepSecurity says the connection "was automatically marked as anomalous because it had never appeared in any prior workflow run." The Backstage team confirmed that workflow is intentionally sandboxed and was not affected.
That last fact is the second case the vendor claim needs. One workflow with restricted egress met the malicious install and did not become an incident.
On the agent side, Claude Code's sandbox gives shell commands "no direct route out," per its sandboxing documentation. Connections pass through a local proxy that checks each host against "your allowed domains, which start empty." InfoQ's November 2025 report on the launch described the same design: network isolation so the agent "can only connect to approved servers."
Pre-flight checks
- Confirm runner type. Per the Harden-Runner README, GitHub-hosted Linux runners get full support. GitHub-hosted Windows and macOS runners get audit mode only. Self-hosted runners require StepSecurity's Enterprise tier. The free tier covers public repositories on GitHub-hosted runners.
- Confirm the agent sandbox can start. On macOS it uses Seatbelt. On Linux and WSL2 it needs
bubblewrapandsocat. Run/sandboxin Claude Code; its panel lists missing dependencies. - Name the list owner and where change requests go.
Step-by-step execution
CI runners
- Add Harden-Runner as the first step of each job, pinned to a commit SHA. This is the README's own example:
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- Run the workflows normally for two to four weeks, covering at least one release build.
- Export the observed destinations. Remove any you cannot explain, and ask the job owner about each one.
- Switch the job to block mode with the reviewed list. Check StepSecurity's documentation for the current input names before you do.
- Verify: add a throwaway step that calls
curl https://example.com. The step should fail. Remove it after.
Agent sandboxes
- Turn on the sandbox with an explicit allowlist and no unsandboxed retries. A starter for a Node project:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"network": {
"allowedDomains": ["registry.npmjs.org", "github.com", "api.github.com"]
}
}
}
- Leave
strictAllowlistoff for the first two weeks. Unlisted hosts then prompt instead of failing, which shows you what is missing. - Collect the hosts people approved. Add the legitimate ones to the list.
- Turn on
"strictAllowlist": trueundernetwork. For a fleet, put the list in managed settings withallowManagedDomainsOnly, so only the managed entries count. - Verify: ask the agent to run
curl --noproxy '*' https://example.com. The docs say it should fail withCould not resolve host. Type nothing yourself: commands entered at the!prompt usually run outside the sandbox.
The unified policy starter
One list, two enforcement points. Keep it in a repository the owner reviews.
EGRESS POLICY v1 owner: ________ reviewed: ______
Tier A Package registries registry.npmjs.org, pypi.org,
files.pythonhosted.org, (your mirror)
Tier B Source control github.com, api.github.com (see warning)
Tier C Your own services artifact store, internal APIs
Tier D Model and agent APIs the provider endpoints your agent needs
(often outside the shell sandbox anyway)
CI runners: A + B + C audit 2-4 weeks -> block
Agent sandboxes: A + B (+ C if needed) prompt 2 weeks -> strictAllowlist
Change process: ticket -> owner review -> add with reason + date
Review cadence: monthly; remove hosts unused for 90 days
These hostnames are an illustrative starting point. Your audit output decides the real list.
How much maintenance to expect
No public dataset in the sources here measures how many destinations a typical CI job or agent session needs, so measure your own before committing. The audit-mode weeks give you the number.
Illustrative example: if a repository's jobs show 12 distinct destinations after four weeks, and 2 of them change in a quarter, the owner handles about one change request a month for that repository. A repository showing 60 destinations, many of them one-off downloads in test fixtures, is telling you to vendor those files or route them through your artifact store first.
Record three numbers per repository at the end of audit mode: distinct destinations, destinations you could not explain, and change requests in the first month of blocking. Those three numbers are the honest cost of the control.
Three failure modes and exact fixes
Failure 1: the build breaks after blocking
A post-install script or test fixture calls a host that only appears in release builds or on a schedule. Fix: extend audit mode to cover a release cycle. Keep the allowlist change process fast; a slow process pushes teams to disable the control.
Failure 2: an allowed domain becomes the leak
The sandbox documentation warns that broad domains such as github.com "can create paths for data exfiltration." The proxy decides from the hostname without inspecting TLS, so domain fronting can reach hosts outside the list. Fix: prefer narrow hostnames. If your threat model needs more, the docs describe a custom proxy that terminates and inspects TLS.
Failure 3: traffic that never touches the sandbox
The sandbox wraps shell commands only. The docs state that local MCP servers, hooks and helper commands "run with your full access," and that built-in tools such as WebFetch follow permission rules instead. Fix: for full coverage, run the agent itself inside a container, VM or the @anthropic-ai/sandbox-runtime with a network allowlist. Tools that ignore proxy settings, such as plain ssh and most database drivers, cannot connect from inside the sandbox at all. Plan for that before blocking.
Rollback and post-change health check
Rollback trigger: a blocked destination stops a release and the owner cannot approve a fix within your release window.
- CI: set the affected job back to
egress-policy: audit. Do not remove the step; you lose the record. - Agents: turn off
strictAllowlistin the scope you set it. Leave the sandbox on. - Add the missing host through the normal change process, then re-enable blocking.
Health check, weekly for the first month: count blocked connections per job. A sudden new destination in a job that has not changed is worth a look, as Backstage's run showed. If an unexplained destination traces back to a dependency, treat the run as compromised and rotate every secret the job could reach, as StepSecurity advises.
The allowlist contains what a known-bad check has not caught yet; the threat.cafe board records the axios incident with its indicators and the finder credited.
Sources
- axios Compromised on npm - Malicious Versions Drop Remote Access Trojan — StepSecurity, 30 March 2026
- Configure the sandboxed Bash tool — Claude Code docs, read 15 October 2026
- Anthropic Adds Sandboxing and Web Access to Claude Code for Safer AI-Powered Coding — InfoQ, 14 November 2025
- step-security/harden-runner README — StepSecurity on GitHub, read 15 October 2026