egress.yml and inputs reference

Every key of the Egress CI policy file, every input and output of the action, the variables it sets for the job and the report it writes.

On this page

egress.yml

The policy is a YAML file in the repository, .github/egress.yml unless the policy input names another one.

.github/egress.yml
version: 1
mode: enforce
presets: [github-actions]
allow:
  - proxy.golang.org
  - "*.npmjs.org"
  - registry.example.com:5000
  - ci-cache.example.com:*
inspect:
  - api.staging.example.com
KeyDefaultMeaning
version1Format version. Only 1 exists.
modeauditaudit: everything passes and what enforce would refuse is reported. enforce: what is outside allow is refused. The mode input overrides it.
presetsnoneNamed lists maintained with the action. The only one is github-actions.
allownoneDestinations the job and its containers may reach.
inspectnoneHosts whose TLS the proxy terminates to scan request bodies for secrets. See Inspection and DLP.

Unknown keys are an error: a typo such as alow: fails the action's step instead of silently allowing nothing. The action checks the file before it starts anything.

Allow rules

RuleMatches
proxy.golang.orgThat name, ports 80 and 443
*.npmjs.orgAny name below npmjs.org (registry.npmjs.org, a.b.npmjs.org), not npmjs.org itself; ports 80 and 443
registry.example.com:5000That name, port 5000 only
registry.example.com:80,5000That name, ports 80 and 5000
ci-cache.example.com:*That name, any port
203.0.113.10That IPv4 address, ports 80 and 443

Names are case-insensitive and a trailing dot is ignored. A wildcard is only valid as the first label (*.example.com).

DNS answers for an allowed name are given to the job; the lock lets non-TCP flows (UDP such as QUIC) through only to the addresses those answers contained. Image pulls are checked against the registry's name on any port.

The github-actions preset

github-actions is the list GitHub publishes for Actions runners in the domains.actions field of api.github.com/meta (external link): the runner's service, checkout, the API, the Actions cache, artifacts and logs, with each Azure storage account by its exact name (never *.blob.core.windows.net, where anyone can create an account). The action adds raw.githubusercontent.com, where setup-* actions read their version manifests.

The action downloads the list at start with the token input and keeps it for 6 hours in the runner user's ~/.cache/egress-ci/. If the download fails, it uses a copy built into the action. The job summary says which one was used.

Inputs

InputDefaultMeaning
policy.github/egress.ymlPath of the policy file.
modeemptyaudit or enforce; overrides mode in the file. Useful to try one file in audit on pull requests and enforce it on main.
lockautoThe nftables lock for connections that ignore the proxy. auto: on when the job reaches root through sudo or Docker, off otherwise. on: like auto, but the step fails when root is not reachable. off: only HTTPS_PROXY is used. In enforce, a lock that cannot be installed fails the step.
containerstracktrack: the job's Docker goes through its own socket, and its containers and builds are held to the policy. lock-all: also refuse, in enforce, every other container's traffic outside the policy (runners that run one job at a time). off: Docker is left as it is. See Docker and builds.
no-proxyemptyExtra comma-separated NO_PROXY entries, for hosts proxy-aware tools should reach directly. With the lock on, those connections still go through the transparent proxy and the policy.
token${{ github.token }}Token used to read api.github.com/meta. It is passed to curl on standard input, never on the command line.
send-eventsoffon: send the job's destinations, blocks and detections to your organization's Egress events. See Send events to OpenWAF.
openwaf-apihttps://dash.openwaf.devOpenWAF API used by send-events. Must be https://.
OutputMeaning
proxyThe proxy URL, http://127.0.0.1:PORT.

What the action sets for the job

VariableValue
HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxyThe proxy URL.
NO_PROXY, no_proxylocalhost,127.0.0.1,::1,172.16.0.0/12,.local,.localhost,.test, your no-proxy, and .blob.core.windows.net when the lock is on (the Actions cache then goes through the transparent proxy, still limited to GitHub's exact storage accounts).
EGRESS_CI_STATEThe sidecar's state directory, $RUNNER_TEMP/egress-ci (the event log is events.jsonl there).
DOCKER_HOST, BUILDX_BUILDERWith Docker tracking: the job's Docker socket and the job builder.
NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, GIT_SSL_CAINFOWith inspect: the run's CA, alone or added to the system bundle.

The sidecar listens on 127.0.0.1 (and on the Docker bridge address for containers) on three ports below the ephemeral range, fixed per runner user: the proxy, the DNS forwarder (next port) and the transparent proxy (the one after).

Decisions and reasons

Every connection, DNS query and image pull gets a decision: allow, audit (outside the policy, let through because the mode is audit) or block. A refused one has a reason:

ReasonWhy
not_allowedThe name, or the port for that name, is not in allow.
sni_mismatchA CONNECT to an allowed name carried a TLS SNI for a name outside the policy.
name_ip_mismatchA connection named an allowed host but went to an address that name does not resolve to (domain fronting).
dlpThe request carried a secret (see Inspection and DLP).
registry_not_allowedAn image pull from a registry outside the policy.
private_destinationLoopback, link-local (such as the cloud metadata address) or multicast, refused in both modes.

In enforce the proxy answers 403 with the header X-OpenWAF-Egress: blocked (or dlp), DNS answers NXDOMAIN, the Docker socket answers 403, and other connections are reset.

The report

The post step appends the report to the job summary and prints report.json in a log group named egress-ci report.json. It holds names, ports, counts and detection ids, never request bodies.

Field of report.jsonMeaning
metaVersion, mode, policy file and its SHA-256, lock and Docker state, where the GitHub list came from, start and stop times, the job's containers.
destinationsOne entry per host: ports, via, containers, connections, bytes_up, bytes_down, allowed, blocked, audited, dns_refused, reasons, detections.
blocked, would_block, dns_refused, detections, dns_queriesTotals.
suggested_allowNames outside the policy, ready for allow.
image_pullsPulls through the job's socket, with their decision.
other_containersContainer flows on the runner that are not this job's.
unattributed_addressesNon-TCP flows to addresses no DNS answer named.

Last updated:

↑↓ to navigate↵ to openesc to close