skip to content

Your build runners now deny all outbound traffic - what belongs on the egress allow-list?

level: middleimportance: should knowfreq 47%

answer

  1. discover before you enforce
  2. the control plane is a destination too
  3. plumbing: resolver, time, revocation
  4. CDN addresses rotate and are shared
  5. one list per stage, not one per company

basics

~20 s

The package proxy, the image registry, source hosts, the CI control plane's log and artifact endpoints, identity and secret endpoints, telemetry, plus plumbing like DNS and time sync. Scope the list per stage rather than keeping one global union.

solid answer

~50 s

Start by discovering rather than guessing: run the policy in log-only mode across a full cycle of pipelines and build the list from what is actually observed. The categories are the internal package proxy, the registry the runner pulls job and base images from, source hosts for clones and submodules, the CI control plane itself (status callbacks, log streaming, artifact and cache upload - forgetting these makes builds fail confusingly at the very end), identity and secret endpoints, any service APIs the tests or deploy step hit, telemetry, and plumbing nobody lists until it breaks like DNS, time sync and certificate revocation endpoints. Express rules by hostname through a proxy, because package indexes and artifact stores sit behind CDNs where addresses rotate and are shared with unrelated tenants. Most importantly, scope the list per stage: the install stage should reach the package proxy and nothing else.

go deeper

for a junior

Know that after deny-all a build still needs to reach specific places to work at all, and be able to name the obvious ones: dependencies, images, source and the CI system itself.

for a middle

Explain how you would build the list without guessing, and why hostname rules through a proxy beat address rules for anything sitting behind a content delivery network.

for a senior

Talk about rollout: audit first, expect the late-failing destinations, keep an exception path open, and scope per stage so the untrusted install step never inherits credential endpoints.

for a principal

Be ready to argue who owns entries and how you prevent slow widening - the failure mode is not a missing rule, it is a list that has quietly become default-allow.

## Why the list is the hard part Flipping build runners to deny-all outbound takes an afternoon. Keeping four hundred pipelines green afterwards is the actual project, and the allow-list is where a well-intentioned control goes to die: every broken build produces a ticket, every ticket produces an entry, and within a quarter the list is the union of everywhere anyone ever went. ## The categories a real list has to carry - **Dependency sources.** Ideally exactly one host: the internal proxy or index that fronts the public ecosystems. If jobs still reach several public indexes directly, you have an allow-list of the whole internet's package hosting. - **Container and base-image pulls.** The registry the runner pulls job images and base layers from, which is often a different host from the one you publish to. - **The CI control plane itself.** Job status callbacks, log streaming, artifact upload, cache service. Forgetting these is memorable because the build appears to *work* and then hangs or fails at the very end when it tries to upload. - **Source hosts.** Clone, submodules, and any dependency declared as a revision on a source host rather than a package version. - **Identity and secret endpoints.** The OIDC token issuer, the cloud STS endpoint, the secret store. Note these are exactly what you want the untrusted install stage *not* to reach - which is the argument for a per-stage list rather than one global list. - **Service APIs used by tests and deploys.** Integration tests hitting a sandbox, the deploy step hitting a control plane. - **Telemetry and observability.** Where build metrics and logs are shipped. - **Plumbing that nobody lists until it breaks.** The DNS resolver, time sync, certificate revocation endpoints that a TLS client fetches, and keyservers used to verify package signatures. ## How you discover it without guessing Run the policy in **log-only / audit mode** first and collect observed destinations across a full cycle of pipelines, then enforce. Two cautions. First, an audit window captures what builds happened to do that week - the quarterly release job, the yearly certificate renewal path and the rarely-run migration pipeline will not appear, so keep a fast exception path for the first few months. Second, audit mode records what builds *do*, which includes destinations you would rather not bless; the list you enforce is a curated subset, not the raw capture. ## Naming destinations: hostname versus address This is the operational trap. Package indexes and artifact stores generally sit behind content delivery networks, so their addresses **rotate and are shared with unrelated tenants**. An IP or CIDR allow-list therefore either breaks on a schedule nobody controls, or gets widened to a whole provider's ranges - at which point it allows an attacker's bucket on that same provider. Address-based rules are honest only for a destination you operate and pin yourself. Hostname rules need a chokepoint that can see the name: - An **explicit forward proxy**, where the client sends the host in the connect request. Reliable, but every build tool has to honour the proxy configuration, and the ones that do not will fail in confusing ways. - **SNI-based filtering** at the network layer, which reads the name from the TLS handshake. This assumes the name is visible in the handshake and that the client is not doing anything clever to hide it. - A **TLS-terminating proxy**, which sees full URLs and payloads, at the cost of installing a trusted certificate authority into every build image and breaking any tool that pins certificates. Wildcards are the other slow leak: one entry covering an entire domain can quietly authorise user-controlled content hosted under it. ## Scoping beats length The fix for list sprawl is not discipline about additions, it is **scope**. Attach the narrow list to the stage that needs it: the install stage gets the package proxy and nothing else; the deploy stage gets the cloud control plane; the test stage gets the sandbox. Entries then get reviewed by the pipeline that owns them, and no single stage inherits the union. ## The answer that lands Name the categories, mention the ones that fail late (control plane, plumbing), say that you would audit before enforcing, and be clear that you would express the rules by hostname through a proxy because CDN-fronted package indexes make address rules either brittle or meaninglessly wide. Then say the list is per-stage, because a single global list is how default-deny becomes default-allow with extra steps.

  • Why is an address-based allow-list a poor fit for a public package index?
    Indexes sit behind content delivery networks, so their addresses change without notice and are shared with unrelated tenants. You end up either firefighting breakage every few weeks or widening the rule to a whole provider range - which then permits an attacker's storage bucket on the same provider.
  • How do you stop the allow-list becoming the union of everything anyone ever needed?
    Scope, not discipline. Attach a narrow list to each stage or pipeline so entries are owned and reviewed by the team that needs them, and no stage inherits everyone else's destinations. A global list with an add-on-request process only ever grows.
  • What does audit mode fail to capture before you enforce?
    Anything that did not run in the observation window - quarterly release jobs, migration pipelines, renewal paths. Plan for a fast exception route for the first few months, and remember that the raw capture also includes destinations you would rather not bless, so the enforced list is a curated subset.

saying these in an interview costs you the question

  • Lists only the package index and forgets the CI control plane
  • Uses IP ranges for CDN-fronted indexes and expects them to hold
  • Keeps one global allow-list shared by every pipeline stage
  • Enforces immediately with no audit period and floods teams with breakage
  • Adds a wildcard covering a whole domain of user-hosted content

context