Skip to content
← Writing

Network Egress Policies for AI Coding Runners Without Breaking Package Installs

5 Jul 2026 · 6 min read

  • AI Coding
  • Runner Security
  • Network Policy
  • Supply Chain
  • Platform Ops

AI coding runners are weirdly privileged by default. The moment a task can call a package registry, curl a random URL, and post somewhere you forgot about, the difference between “installed a dependency” and “quietly exfiltrated a secret” becomes one permissive firewall rule.

The hard part is that fully closed egress is not realistic for most real builds. Agents need registries, Git remotes, artifact stores, maybe a model gateway, maybe an internal API. If you block everything, the automation looks secure but it does not ship.

This post walks through the network policy pattern I would actually use for AI coding runners: a deny-by-default runtime lane, a short bootstrap window for dependency fetches, DNS-aware allowlists, and auditable exceptions that do not quietly become permanent.

Why this matters

A lot of AI coding risk is framed as prompt risk, approval risk, or write access risk. Those are real, but network egress is where hidden damage tends to leave the machine. If a runner can reach arbitrary hosts, then secrets in environment variables, checked-out code, test fixtures, and generated artifacts all become movable.

The production problem is not just theft. Open egress also makes builds less reproducible, lets one flaky third-party dependency tank many runs, and makes incident response miserable because you cannot easily answer where a runner talked to during a bad session.

For most teams, the right goal is not “no network ever.” It is “only the network paths this task class should need, for only as long as it needs them.”

Architecture or workflow overview

flowchart LR
  A[Task queued] --> B[Runner starts in deny-all lane]
  B --> C{Needs bootstrap?}
  C -->|yes| D[Bootstrap allowlist
registries + git + DNS]
  C -->|no| E[Execution allowlist
internal APIs + model gateway]
  D --> F[Dependency fetch complete]
  F --> E
  E --> G[Code execution + tests]
  G --> H[Audit log + flow record]
  H --> I[Runner teardown]
  1. Start every runner in a deny-all outbound lane.
  2. Open a short-lived bootstrap lane only when dependency install or fetch is actually needed.
  3. Collapse to a narrower execution lane before running untrusted code, tests, or generated scripts.
  4. Record every destination class so you can prove what the runner did later.

Implementation details

1) Split bootstrap access from execution access

The cleanest mistake to avoid is one forever-open allowlist that handles both package installation and runtime execution. Install-time needs are usually broader than execution-time needs.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ai-runner-execution-egress
spec:
  podSelector:
    matchLabels:
      app: ai-runner
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: internal-gateway
      ports:
        - protocol: TCP
          port: 443
    - to:
        - ipBlock:
            cidr: 10.20.0.0/16
      ports:
        - protocol: TCP
          port: 443
    - to:
        - namespaceSelector:
            matchLabels:
              name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

That execution lane is intentionally boring. It lets the runner reach DNS, an internal gateway, and approved internal services. No direct public internet beyond what the task truly needs.

For bootstrap, I prefer a separate lane or sidecar-controlled transition rather than editing one huge policy in place.

#!/usr/bin/env bash
set -euo pipefail

BOOTSTRAP_SET=runner_bootstrap_allow
ipset create "$BOOTSTRAP_SET" hash:ip -exist

for host in registry.npmjs.org pypi.org files.pythonhosted.org github.com api.github.com; do
  dig +short "$host" | grep -E '^[0-9.]+' | while read -r ip; do
    ipset add "$BOOTSTRAP_SET" "$ip" -exist
  done
done

iptables -I OUTPUT -m set --match-set "$BOOTSTRAP_SET" dst -p tcp -m multiport --dports 80,443 -j ACCEPT

The point is not that these exact hosts cover every stack. The point is that dependency fetches are explicit, short-lived, and observable.

2) Use destination classes, not ad hoc hostname exceptions

Teams often start with one-off exceptions like “allow this random artifact host because a run failed yesterday.” That grows into a mess. I prefer policy classes such as package-registry, git-remote, model-gateway, internal-api, and artifact-upload.

{
  "runnerClass": "repo-patch",
  "egressClasses": [
    "dns",
    "git-remote",
    "package-registry",
    "model-gateway",
    "artifact-upload"
  ],
  "bootstrapTtlSeconds": 240,
  "denyUnknownDestinations": true
}

That policy object is easier to reason about than fifty hostnames pasted into a firewall config. It also gives reviewers a vocabulary for asking whether a task really needs a class at all.

3) Treat denied egress as debugging signal, not just breakage

When a runner suddenly tries to call a host you never expected, that is useful evidence. Maybe a dependency added a new CDN, maybe a test is hitting production by mistake, or maybe the generated code is doing something you should inspect immediately.

$ curl https://paste.rs
curl: (7) Failed to connect to paste.rs port 443: Operation not permitted

$ npm ci
added 842 packages in 19s

$ ./verify.sh
PASS api contract tests
PASS lint
PASS focused integration suite

The ideal outcome is not zero denials. It is denials that are legible enough to separate legitimate bootstrap gaps from suspicious behavior.

What went wrong and tradeoffs

Policy style Good at Bad at When I would use it
Fully open egress Convenience, low friction Exfil risk, weak auditability, reproducibility drift Almost never for agent runners
Static allowlist only Stronger control Breaks package installs and CDN-backed ecosystems Tiny, fully mirrored environments
Staged bootstrap plus narrowed execution Balanced safety and practicality More moving parts, needs automation discipline Most production AI coding runners
Air-gapped with internal mirrors Highest containment Operationally heavy, slower to maintain High-regulation or high-value codebases

One failure mode I have seen is DNS drift. You allow a registry hostname, but the underlying CDN or storage endpoint changes and bootstrap starts failing. If your policy engine only understands raw IPs, you need a refresh loop and a clear TTL story.

Another tradeoff is supply-chain practicality. Some ecosystems pull from more places than teams realize. Node may hit the registry, GitHub tarballs, and postinstall scripts that try to call home. Python can involve direct file hosts. Container builds may need base image registries and signature verification endpoints.

Pitfall: do not let the bootstrap lane stay open for the whole task just because it is easier. The dangerous window is after dependencies arrive, when generated code and test hooks begin executing with the broadest network reach.

There is also a security nuance around model gateways. If your coding runner can call a remote LLM endpoint directly, that endpoint is part of your egress policy whether you think of it as infrastructure or not. Treat it as a sensitive class with logging, auth, and rate limits.

What I would not do is maintain hand-edited firewall exceptions per repository. That scales badly and turns urgent build fixes into policy drift. I would encode task classes once, version them, and make exception requests expire automatically.

Practical checklist

  • Start runners in deny-all outbound mode.
  • Separate bootstrap egress from execution egress.
  • Group destinations into reviewed policy classes.
  • Log denied destinations with enough context to investigate later.
  • Expire bootstrap allowances automatically after install or fetch.
  • Mirror the most common registries internally if you want tighter execution lanes.
  • Treat model gateways, artifact uploads, and Git remotes as first-class egress classes.
  • Review unknown outbound attempts as either dependency drift or suspicious behavior.

Conclusion

Network egress policy is one of the least flashy controls in AI coding infrastructure, but it does a lot of real work. It keeps dependency access functional, shrinks the blast radius of generated code, and gives you a credible story when someone asks where a runner could actually send data.

If you separate bootstrap from execution and make outbound access explicit instead of ambient, your automation gets both safer and easier to debug.