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
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.orgAdd 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-allWith 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
allowline. - A name that only appears sometimes: run the job in audit (the
modeinput) a few times and compare summaries. - Switch off quickly: set
mode: auditin the input or the file. To turn it off without a commit, read the mode from a repository variable:mode: ${{ vars.EGRESS_MODE || 'enforce' }}, thengh variable set EGRESS_MODE -b audit.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
policy .github/egress.yml not found | The action runs before actions/checkout, or the file has another path: use the policy input. |
field … not found in type main.Policy | A typo in a key of egress.yml. |
enforce needs the lock | Root 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 Docker | lock: "on" on a runner where the job user has neither passwordless sudo nor Docker. |
| A tool fails with a certificate error | The host is under inspect and the tool does not read the CA variables. See Inspection and DLP. |
| The cache step is slow or refused | It needs the github-actions preset: the Actions cache uses GitHub's storage accounts. |
An allowed name is refused with name_ip_mismatch | The 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. |