Skip to content
buildcagePublic

About

GitHub Action to build Docker images with outbound network access restricted to an allowlist

Resources

Security policy

Stars

11 stars

Watchers

0 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

 

History

2,275 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Buildcage for Docker

Buildcage

GitHub Marketplace version build test license

A docker build runs your RUN steps, and every dependency they fetch, with unrestricted network access. Buildcage puts that behind an allowlist: each RUN step can reach only the destinations you name, and anything else is refused and reported.

  • Your Dockerfile doesn't change, BuildKit isn't patched, and nothing Buildcage does is left in the image layers.
  • Run once in audit mode and the report writes the allowlist for you, ready to paste back into the workflow.
  • A rule can name an HTTP method and a URL, inside TLS as well, so a build can fetch a package from a registry without being able to publish one to it.
  • It all runs inside your GitHub Actions job, with no agent and no external service.

See buildcage.github.io for what it does and why. To isolate a workflow run: step rather than a Docker build, use Buildcage for run: Steps.

Contents

Requirements

The builder is a container on the runner itself, so this action needs a Linux runner with a working Docker installation:

  • GitHub-hosted
    • ubuntu-latest, the versioned ubuntu-* images, and their -arm variants
    • Lightweight images such as ubuntu-slim are not supported: they ship a Docker client with no daemon
  • Self-hosted
    • Docker Engine 25.0 or later, with Compose v2.20.2 or later
    • A host using cgroup v2
    • For the inspect engine, a Docker data root that can hold an overlayfs upper directory: not itself on overlayfs (Docker-in-Docker needs a volume for /var/lib/docker) and not on XFS formatted with ftype=0

A runner that falls short fails while the builder starts, before any RUN step runs.

Usage

Buildcage starts a BuildKit builder in your job. Point Docker Buildx at it as a remote driver and build as usual. Run once in audit mode to collect what the build reaches, then switch to restrict. The examples below use the default inspect engine; Engines covers the choice between the two.

1. Find out what the build reaches

- name: Start Buildcage in audit mode
  uses: buildcage/docker@fb8ec1accd272b4a9c38483989af89dbb23161a6 # v4.1.0
  with:
    proxy_mode: audit # Log every destination, block nothing

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
  with:
    driver: remote
    endpoint: docker-container://buildcage

- name: Build
  uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
  with:
    context: .

- name: Show Buildcage report
  if: always()
  uses: buildcage/docker/report@fb8ec1accd272b4a9c38483989af89dbb23161a6 # v4.1.0

The report action writes every destination the build contacted to the Job Summary:

Outbound Traffic Report (audit mode)

Its Switch to restrict mode section holds the allowlist, already written out from what the build actually did. A request whose method, host or path a rule would read as a wildcard is listed under it rather than written in, since copying it would permit more than was sent.

2. Enforce the allowlist

Paste that allowlist into the setup step and switch the mode:

- name: Start Buildcage in restrict mode
  uses: buildcage/docker@fb8ec1accd272b4a9c38483989af89dbb23161a6 # v4.1.0
  with:
    proxy_mode: restrict
    allowed_url_rules: |
      GET http://deb.debian.org/**
      GET https://registry.npmjs.org/**
      POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk

Each rule names the methods it permits, so this one lets npm fetch packages without letting it publish any: a POST to the same host is refused, as is every host not listed.

Outbound Traffic Report (restrict mode)

A blocked connection fails the job at the report step, so a build that starts reaching somewhere new doesn't pass unnoticed.

Example workflows

Each pair builds the same Dockerfile with and without rules: inspect on an apt and npm build (audit · restrict), universal on a Maven build (audit · restrict).

Engines

proxy_engine sets how closely a build's traffic is read. inspect is the default; universal has to be set explicitly.

inspect terminates TLS and re-signs it with a CA generated for each builder container. Rules match on method and URL, so GET|HEAD https://registry.npmjs.org/** allows a fetch while refusing a publish on the same host, and the report names every request with its URL. The build has to trust that CA: Buildcage adds it to the system store, to the CA-trust variables, to a JVM already in the base image, and to Chromium's NSS database, so most toolchains need nothing extra (see CA trust and compatibility).

universal reads only the SNI. Rules match on host and port, so registry.npmjs.org:443 is the most one can say, the report shows host and port, and the build's own TLS is left untouched.

Start with inspect. Reach for universal when a host's certificate cannot be re-signed, such as a tool that pins one or ships a trust store Buildcage cannot inject into: either for the whole build, or for that host alone with an allowed_tls_rules passthrough.

Both intercept at the network level, so a tool that ignores HTTP_PROXY is covered either way, and both apply to RUN steps. What buildkitd fetches for itself stays outside: see Limitations.

Inputs

Every input is optional, and the ones below are the rules you write by hand. The full list, with defaults and the engines each input applies to, is in Reference, and the grammar those rules are written in is in Rule syntax.

The builder is named buildcage unless builder_name says otherwise, and the Buildx endpoint has to match whatever it is named.

The inputs can also live in a YAML file next to the Dockerfile, named by config_file. See Config file.

Operation modes

proxy_mode: audit logs every destination the build reaches and blocks nothing. restrict, the default, allows only what the rules match and blocks and logs everything else. Start with audit when you first adopt Buildcage or when a dependency changes, and keep restrict for everyday builds.

If you forget a domain the build needs, restrict blocks it and the report step fails with the destination named, which is why it is worth running audit first.

audit also lets a connection made straight to an address through. Under universal that includes cloud metadata (169.254.169.254) and the runner's own addresses; inspect still refuses those unless a rule names the address as its host.

Rules for the inspect engine

allowed_url_rules is the one to reach for. Each line is a method list, a space, and a URL pattern. * stays inside one domain label or path segment, ** crosses dots and slashes, and a rule with no path allows any path on that host:

allowed_url_rules: |
  # apt
  GET http://deb.debian.org/**

  # npm: fetch packages, and the audit endpoint it posts to
  GET https://registry.npmjs.org/**
  POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk

  # pip: one registry, two domains
  GET https://pypi.org/simple/**
  GET https://files.pythonhosted.org/packages/**

  # a private registry is an ordinary host
  GET|HEAD https://registry.internal.example.com:8443/**

allowed_tls_rules passes a TLS destination through undecrypted, judged on its SNI and port. It is for TLS that isn't HTTPS, and for the hosts an inspect build must not decrypt:

allowed_tls_rules: |
  db.example.com:5432
  repo.maven.apache.org:443

allowed_ip_rules covers connections made straight to an address, which never go through DNS. A rule may be an address, a CIDR block or a wildcard, on either engine:

allowed_ip_rules: |
  192.168.1.10:443
  10.0.0.0/8:443

Rules for the universal engine

universal never decrypts, so rules name a host and a port. allowed_https_rules and allowed_http_rules split by scheme, and allowed_ip_rules works as it does under inspect:

allowed_https_rules: |
  # npm and maven
  registry.npmjs.org:443
  repo.maven.apache.org:443
  *.internal.example.com:443  # everything on the internal network

allowed_http_rules: |
  deb.debian.org:80

allowed_ip_rules: |
  192.168.1.10:443

A # starts a comment at the start of a line or after whitespace, so a group of rules can carry a heading or a rule can carry its reason. It works the same in every rule input.

Destinations you expect to stay blocked

A noisy dependency, or a domain you are deliberately keeping off the allowlist to confirm it stays blocked, belongs in known_blocked_rules. Those rows are marked Expected in the report and stop failing the job, and the destination stays unreachable:

known_blocked_rules: |
  telemetry.example.com

A name refused at resolution, before any connection, has no port. A bare telemetry.example.com (or telemetry.example.com:*) covers it; telemetry.example.com:443 does not, since no port was involved.

One rule per line, and on inspect a line can also be a URL rule (a method and a URL, the allowed_url_rules syntax) to acknowledge a single endpoint on a host whose other traffic is allowed — a telemetry POST to an API you otherwise use, say:

known_blocked_rules: |
  POST https://api.example.com/telemetry

Any other blocked request to that host still fails the step. See Blocked rules for the full syntax.

Report action

buildcage/docker/report reads the builder's communication log and writes the Job Summary. Add it with if: always() so a failing build still reports:

- name: Show Buildcage report
  if: always()
  uses: buildcage/docker/report@fb8ec1accd272b4a9c38483989af89dbb23161a6 # v4.1.0

Whatever was refused is listed under Blocked Hosts with the reason, and, under inspect, Communication details names the URL of every request, allowed or refused, with credential query parameters replaced (see Credentials in a URL). In audit mode the summary also holds the Switch to restrict mode allowlist.

In restrict mode the step fails when a blocked connection is found, and with it the workflow. Pass fail_on_blocked: false to report without failing, or list what you expect to stay blocked in the setup action's known_blocked_rules. In audit mode nothing fails the step. The action's inputs are in Reference.

Traffic artifact

upload_traffic_artifact: true uploads the whole timeline as a traffic.json, one row per request and per name lookup, with the method, URL, status, size and the address it resolved to. It is uploaded even when the build fails. Under universal it omits the method, URL, status and resolved address that only inspect sees. The fields are listed in Reference.

How it works

How Buildcage restricts what a build can reach

BuildKit itself is unpatched. Buildcage starts it with a network configuration that puts every RUN step on its own network, and wraps its runtime so the build CA is mounted into a step for as long as that step runs. Name lookups and traffic from there reach the resolver and the proxy in the builder container, at the network level, so a tool that ignores the proxy environment variables is covered as well. The figure is the inspect engine; universal follows the same path without terminating TLS, and so needs no CA.

Buildx needs driver: remote because the builder is a second BuildKit, but multi-stage builds, caching and the image that comes out are unaffected, and nothing Buildcage does reaches the LLB or a cache key.

Security Details has the architecture of each engine, with a diagram of what runs where and what it decides.

CA trust and compatibility

proxy_engine: inspect terminates TLS and re-signs it with a CA generated for each builder container, whose private key never leaves that container (details), so the build has to trust that CA. As each RUN step starts, Buildcage points the variables the common toolchains read at a store that holds it: NODE_EXTRA_CA_CERTS, DENO_CERT, SSL_CERT_FILE, REQUESTS_CA_BUNDLE and PIP_CERT. CURL_CA_BUNDLE is set only in a step with no system CA store of its own, since curl reads that store already. A variable the base image or the Dockerfile already set is appended to rather than redirected. GIT_SSL_CAINFO, npm_config_cafile, AWS_CA_BUNDLE, CARGO_HTTP_CAINFO and BUNDLE_SSL_CA_CERT are appended to the same way when set, and otherwise left unset. Neither the CA nor the variables are left in the image layers, except a copy a step hides where it cannot be read (see Limitations). The CA is also left in the distribution's own anchor directory, so a step that installs ca-certificates partway through keeps trusting it once update-ca-certificates has rebuilt the bundle from scratch. On SUSE, GnuTLS reads /var/lib/ca-certificates/pem instead of the bundle, so the CA goes into that directory for the step as well. A JVM already in the base image reads none of those variables and only its own keystore, so the CA is added there too, to $JAVA_HOME/lib/security/cacerts and that of the first java on PATH, in whichever shape it ships (JKS or PKCS#12), for the step and taken back out before the layer is committed, letting mvn, gradle and java reach the proxy without proxy_engine: universal. Chromium, including the chrome-headless-shell that Puppeteer, Playwright and Remotion download, reads only its compiled-in root store and the NSS database in $HOME, so for each step that database's pkcs11.txt gains a read-only slot on a database holding only the CA, taken back out before the layer is committed. The database itself stays the step's own, with whatever the step writes to it.

The full table, with what each variable points at when the step has a system CA store and when it has none, is in Reference. What this cannot cover is in Limitations, below.

Scope

Buildcage controls where your build can connect, not what code it runs. A malicious package delivered through an allowed domain still runs. Treat it as one layer in a defense-in-depth strategy, a last line of defense so that if something slips through your other measures, at least it can't call home.

An allowlist also cannot stop anything leaving through a service you had to allow anyway. That is a structural limit. What it does stop is traffic to a destination that is not on the list, and infrastructure an attacker set up is normally not on it, because the build has no reason to reach it. That is also the hardest kind of leak to find afterwards.

An allowlist generated from an audit run already blocks every destination the audit did not record. Whether to go further depends on what the build has access to: Hardening is what to look at when it holds credentials, personal data, or source you do not publish. For the full threat model, see Security Details.

Limitations

What isn't covered

  • buildkitd's own traffic is outside the allowlist. FROM, ADD <url>, git contexts and the frontend image a # syntax= directive names are fetched by buildkitd itself rather than by a RUN step, and no rule applies to them. See What buildkitd fetches itself.
  • allowed_tls_rules is not decrypted. The SNI and the port are checked, and the proxy resolves that name itself, so the connection reaches the host the rule named, but nothing inside the TLS session is seen.
  • allowed_ip_rules is not inspected at all, and doesn't require TLS either: once an ip:port pair is allowed, any TCP-based protocol can use that path. Prefer a domain rule wherever the destination has a stable name.
  • universal never sees the method or the path. They travel inside TLS, so neither is enforced and neither reaches the report or the traffic artifact. A request fronted behind an allowed SNI is invisible to it as well, while inspect matches on the real Host and refuses it. See Domain fronting.

Protocols

  • UDP is dropped, so QUIC and HTTP/3 either fall back to TCP or fail. Port 53 to the proxy, which is the resolver, is the one exception. ICMP is dropped too.
  • IPv6 is not used anywhere. The rule syntax refuses an IPv6 address, forwarded IPv6 is dropped, and the proxy reaches allowed names over IPv4 only, so an allowed name with AAAA records and no A record never resolves and no rule can clear it.

Service discovery

The resolver has no upstream, so it returns nothing for a discovery record: SRV, TXT, TLSA and URI queries come back empty, and the build connects to the name a rule allowed rather than to one a nameserver picked for it. Clients that treat SRV as a discovery layer fall back to the host name itself, so the host a rule names is the host the build reaches.

What this breaks is a client with no fallback, where the record is the only way it can find the service at all. A mongodb+srv:// connection string is the one to expect: use mongodb:// with the shard hostnames written out and allowlist those instead. Active Directory and Kerberos discovery have the same shape.

Under inspect, a lookup for a _service._proto.<host> name is reported as discovery when the rules allow that host, and is not counted as blocked. A service name under any other host is reported as blocked; see Blocked service names.

Under the inspect engine

  • A tool that pins a specific certificate, or ships a bundled trust store it never lets the system update, still needs proxy_engine: universal or an allowed_tls_rules passthrough: inspect re-signs the connection, and a pinned or bundled store will not accept the new certificate.

  • A client that insists on HTTP/2 through ALPN, as gRPC clients do (grpc-go since 1.67, for one), fails to connect: inspect answers no ALPN. Pass its host through with allowed_tls_rules, which judges the SNI and port only. A client that falls back to HTTP/1.1, such as curl, works.

  • The JVM (Java, Kotlin, Scala) reads only its own keystore rather than the CA-trust variables. A JDK already in the image at $JAVA_HOME or behind the first java on PATH is handled: the CA is added to its keystore for the step and removed before the layer is committed. A keystore sealed with a password other than the JDK default still falls back to proxy_engine: universal: Buildcage will not rewrite it. No other JDK trusts the CA, such as one further along PATH, a Gradle toolchain under ~/.gradle/jdks or one the step itself downloads (an sdk install, an unpacked tarball). Fetch it in an earlier RUN step and put it first on PATH or on JAVA_HOME with ENV:

    RUN tar xzf jdk-21.tar.gz -C /opt/java && /opt/java/jdk-21/bin/java -jar fetch.jar   # fails
    RUN tar xzf jdk-21.tar.gz -C /opt/java
    ENV PATH=/opt/java/jdk-21/bin:$PATH
    RUN java -jar fetch.jar                                                              # fine
  • Chromium trusts the CA through a slot added to the NSS database it reads: ~/.pki/nssdb when it exists, else ~/.local/share/pki/nssdb, else a new ~/.pki/nssdb, which Chromium then fills and the image keeps, as it would under a Chromium before M146. A Chromium before M146 does not read ~/.local/share/pki/nssdb, so use M146 or later where that is the database. A new database belongs to the home's owner, so under someone else's home, such as www-data's /var/www, Chromium does not open it and does not trust the CA. An existing database the step's user cannot write, such as root's used after USER, takes no slot, since Chromium would not open it either, and the step warns; use proxy_engine: universal for Chromium there.

  • Only the NSS database under the HOME the step starts with carries the slot. Chromium started with another HOME (HOME=/tmp chromium, export HOME=...) or as another user (su, gosu, sudo -u) does not trust the CA. Switch users with USER instead, which the slot follows as long as no ENV HOME pins the home, and leave HOME alone within the step. A Firefox carrying Mozilla's own root list, such as Playwright's or the one Selenium drives, reads a per-profile database and does not trust the CA; use proxy_engine: universal or an allowed_tls_rules passthrough for it.

  • A step that changes the CA's own trust in the NSS database (certutil -M), or exports it and imports it back, copies the CA into the step's database, which fails the build as a copy the wrapper cannot take out. A step cannot remove the database's directory (rm -rf ~/.pki) either, since the step sees it as a mount point. Every RUN step has this mount point, created if the home has no database, so git clone <url> . into the home and rm -rf ~/.[!.]* both fail; use a WORKDIR outside the home. A copy of the home (cp -a ~ /backup) has the slot cut out of its pkcs11.txt before the layer is committed.

  • A RUN step that copies the system CA bundle into a binary or an uncompressed archive (go:embed, include_str!, tar cf) fails: the copy carries the build's CA, which cannot be cut out of a binary without corrupting it. Copy the bundle in an earlier RUN step instead:

    RUN cp /etc/ssl/certs/ca-certificates.crt ./certs/ && go build   # fails
    RUN cp /etc/ssl/certs/ca-certificates.crt ./certs/
    RUN go build                                                      # fine
  • A copy of the CA left in a step's layer is removed. One that cannot be, such as in a JKS keystore sealed with a password other than changeit, fails the build (fail_on_ca_residue: false makes it a warning, see CA residue). A copy that cannot be read, in a compressed archive or a keystore encrypted under a password other than none or changeit or naming more than a million key-derivation iterations, is not found and stays in the image, as is one hex-dumped or re-encoded as base64 outside a PEM block in lines shorter than 48 characters. See Security Details.

  • audit terminates TLS as well. It drops the rules, not the interception, so a tool that cannot accept the CA fails in audit exactly as it would in restrict.

  • An image with no system CA store (scratch, distroless, or debian:*-slim before ca-certificates is installed) still gets every variable set, but pointed at a file trusting only the proxy's own CA. That is enough for ordinary HTTPS, since inspect re-signs all of it with that CA, but not for an allowed_tls_rules or allowed_ip_rules passthrough, which presents its own real certificate. The decision is made once, from the rootfs as the step begins, so installing ca-certificates partway through a step doesn't help a passthrough made later in the same step:

    RUN apt-get install -y ca-certificates && \
        curl https://internal.example.com/pkg.tgz -o pkg.tgz   # still fails: CURL_CA_BUNDLE was already
                                                               # fixed to the proxy-CA-only fallback
    
    RUN apt-get install -y ca-certificates
    RUN curl https://internal.example.com/pkg.tgz -o pkg.tgz   # this step starts with a store, so
                                                               # CURL_CA_BUNDLE points at it instead
  • A task runner that starts each command with a filtered environment drops the CA variables before the command sees them. vite-plus does this for a cached vp run task, which keeps NODE_OPTIONS but not NODE_EXTRA_CA_CERTS, so Node inside it fails with SELF_SIGNED_CERT_IN_CHAIN. In an image with a system CA store, which holds the CA already, have Node read that store. As an ARG the flag reaches every later RUN in the stage without staying in the image, unless an ENV NODE_OPTIONS overrides it:

    ARG NODE_OPTIONS=--use-system-ca   # Node 22.15+ (23.9+ on 23.x); older Node refuses to start
    RUN vp run build

    Otherwise pass the variable through on the task itself, as untrackedEnv rather than env, which would put its value in the cache key. A task with cache: false keeps the whole environment.

    // vite.config.ts
    build: { command: "vp build", untrackedEnv: ["NODE_EXTRA_CA_CERTS"] },
  • The CA goes into a copy of each directory holding a bundle it is added to (the CA store's, and each one a CA variable points into), and the copy is mounted over the directory for the step's duration. When one of them holds another, only the outer one is mounted. Removing or renaming a mounted directory fails, while what is inside it behaves normally and what the step writes there is copied back when it ends:

    RUN rm -rf /etc/ssl/certs        # fails: the directory is a mount point
    RUN rm -rf /etc/ssl/certs/*      # fine

    The same goes for the directory holding the keystore of a JDK at $JAVA_HOME or behind the first java on PATH, unless it is inside the CA store's mount. A JDK with a keystore of its own has its lib/security mounted, so a step cannot remove that JDK; take it off PATH and JAVA_HOME with ENV first. A distribution JDK links to a shared keystore: Debian's is inside /etc/ssl/certs and adds no mount point, while on RHEL, Fedora and their derivatives /etc/pki/ca-trust/extracted/java is one.

  • A custom CA path that is unexpectedly large (more than 20 MiB or 512 files) has injection skipped for that variable only, the same degradation as when no CA bundle is found at all. A system CA bundle whose directory holds more than 64 MiB or 4,096 files, or is the container root, is treated as no bundle at all.

  • Neither engine produces SLSA provenance. The traffic artifact is an observation record with no content digest.

On the runner itself

An allowlisted name that resolves to cloud metadata, to loopback, or to an address the runner itself holds is refused, so a compromised name cannot turn the proxy into a route back into the runner. A mirror or registry running on the runner is therefore not reachable by name: allow it with allowed_ip_rules, which skips that guard except for the proxy's own port. See A name may not resolve inward.

What the audit allowlist covers

The generated allowlist covers only what the engine classified. allowed_tls_rules and allowed_ip_rules come back exactly as the audit run was configured with them, since nothing behind a passthrough was ever decrypted.

FAQ

Can I keep inspect but leave a few hosts undecrypted?

Yes, that is what allowed_tls_rules is for. The SNI and port are checked and the connection passes through untouched, so a JVM build or a tool that pins a certificate can sit inside an otherwise inspected build. Those hosts are enforced at host-and-port granularity, the same as universal.

A host only ever gets looked up, never connected to. How do I write a rule for it?

The report gives it a row with DNS as the rule kind and no port. If you want it to stay unreachable without failing the job, put the name in known_blocked_rules, which is the one input where a rule may omit the port. If the build actually needs it, write an ordinary host or URL rule and the lookup is reported as allowed.

One registry needs several domains. How do I find them all?

Run audit and read the report. PyPI, for example, uses both pypi.org and files.pythonhosted.org, and the audit report lists every domain the build touched, so the generated allowlist already has them.

Which engine should I start with?

inspect, unless something in the build carries its own trust store. It is the only engine that can tell a fetch from a publish on the same host. See Engines.

Why not use BuildKit's built-in --proxy-network?

BuildKit's exec network proxy injects HTTP_PROXY/HTTPS_PROXY into each RUN step and can record what it fetched as SLSA provenance, which Buildcage does not do. Two limits kept it from being the enforcement mechanism. Its source policy matches a request's host, port and URL path but has no notion of an HTTP method, so it cannot allow a fetch from a registry while refusing a publish to the same host, which inspect does by terminating TLS. And because it is an explicit proxy, what it enforces and records reaches only tools that honor the proxy variables; a tool that ignores them is not covered. Buildcage enforces at the network level instead, so it also covers those tools and can act on the method and URL.

GitHub's native egress firewall

GitHub is building an egress firewall directly into Actions runners (technical preview as of September 2026): opt a job into a firewall-enabled runner image and its traffic is inspected outside the runner VM, in log or enforce mode, from a single .github/egress-firewall.yaml in the repository. Because it sits outside the VM, a workflow that gains root inside the runner cannot switch it off. Firewall-enabled images are GitHub-hosted and Linux only.

One policy for the whole run is one allowlist for every step in it: the destinations actions/checkout, the caches and the setup actions need stay open to the build as well. Buildcage writes a separate allowlist for the build you don't trust, so a docker build gets the hosts that build needs and nothing else, and a rule there can name a method and a URL rather than only a host. The two compose: a perimeter the job can't switch off, and a tighter policy inside it.

Buildcage also runs on any Linux runner with Docker, self-hosted included, rather than on a firewall-enabled runner image.

Documentation

Doc What's in it
Reference Every input, the rule syntax in full, and the report's own output
Security Details Architecture and threat model for every engine, attack resistance
Development Guide Local usage, testing, logs, and the repository layout

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests at github.com/buildcage/docker.

Show Your Support

Knowing that this project is useful to others gives me the motivation to keep working on it. If you find Buildcage helpful, please consider giving it a star ⭐ on GitHub!

Disclaimer

This software is provided "as is", without warranty of any kind, express or implied. The authors and contributors are not liable for any damages, losses, or security incidents arising from the use of this software. Use at your own risk.

License

The Buildcage source code is licensed under the MIT License. See LICENSE file for details.

The Docker image includes third-party components under their own licenses (GPL, Apache 2.0, ISC, etc.). See THIRD_PARTY_LICENSES for the full list.

The Actions bundle their npm dependencies (MIT, Apache 2.0, ISC) into the committed dist/ files. See THIRD_PARTY_LICENSES_NPM for their license texts.

About

GitHub Action to build Docker images with outbound network access restricted to an allowlist

Resources

Security policy

Stars

11 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages