From audit to enforce

Build a job's allowlist from audit runs, check it on pull requests and enforce it, without breaking the pipeline.

On this page

A new policy starts in audit: nothing is refused and the job summary shows what enforce would refuse. Once the summary is clean for the jobs you care about, enforce refuses the rest.

1. Run in audit

.github/egress.yml
version: 1
mode: audit
presets: [github-actions]
allow: []

Run every workflow that uses the action at least once, including the jobs that only run on main, on tags or on a schedule: a job's destinations depend on what it does.

2. Copy the names

Each job summary has a collapsed block Names outside the policy, already in YAML:

allow:
  - proxy.golang.org
  - sum.golang.org
  - registry.npmjs.org

Add them to allow, after checking that each one is expected. A name you do not recognise is the point of the exercise: find which step reached it before allowing it. The Via column helps: direct means a tool ignored HTTPS_PROXY, container names the container.

Replace many names under one domain by a wildcard only when the names vary between runs (CDN shards, per-region hosts). *.example.com allows every name below example.com, which is more than the job needs.

3. Check on pull requests

The mode input overrides the file, so one policy can be enforced on pull requests while main still only reports:

- uses: openwaf-dev/egress-action@v0.3.2
  with:
    mode: ${{ github.event_name == 'pull_request' && 'enforce' || 'audit' }}

A refused connection fails the step that made it (the tool gets 403, NXDOMAIN or a reset), and the summary says which name and why.

4. Enforce

Set mode: enforce in the file. For a job on a GitHub-hosted runner, also ask for the lock and for every container to be held to the policy:

- uses: openwaf-dev/egress-action@v0.3.2
  with:
    lock: "on"
    containers: lock-all

With lock: "on" the step fails when root is not reachable, instead of running with only the proxy; in enforce, a lock that fails to install fails the step too. lock-all is safe on GitHub-hosted runners, which run one job per machine.

Keeping the policy current

  • New dependency or tool: the first run in enforce fails on the new name; the summary gives the allow line.
  • A name that only appears sometimes: run the job in audit (the mode input) a few times and compare summaries.
  • Switch off quickly: set mode: audit in the input or the file. To turn it off without a commit, read the mode from a repository variable: mode: ${{ vars.EGRESS_MODE || 'enforce' }}, then gh variable set EGRESS_MODE -b audit.

Troubleshooting

SymptomCause and fix
policy .github/egress.yml not foundThe action runs before actions/checkout, or the file has another path: use the policy input.
field … not found in type main.PolicyA typo in a key of egress.yml.
enforce needs the lockRoot was reachable but the nftables lock could not be installed, usually because nft is missing on a self-hosted runner. See self-hosted runners.
lock: on needs sudo or Dockerlock: "on" on a runner where the job user has neither passwordless sudo nor Docker.
A tool fails with a certificate errorThe host is under inspect and the tool does not read the CA variables. See Inspection and DLP.
The cache step is slow or refusedIt needs the github-actions preset: the Actions cache uses GitHub's storage accounts.
An allowed name is refused with name_ip_mismatchThe connection went to an address that name does not resolve to: an entry in /etc/hosts, a hard-coded address or a tool's own DNS over HTTPS. Let the tool resolve the name through the system resolver.

Last updated:

↑↓ to navigate↵ to openesc to close