# 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.

## egress.yml

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

```yaml
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
```

| Key | Default | Meaning |
|---|---|---|
| `version` | `1` | Format version. Only `1` exists. |
| `mode` | `audit` | `audit`: everything passes and what enforce would refuse is reported. `enforce`: what is outside `allow` is refused. The `mode` input overrides it. |
| `presets` | none | Named lists maintained with the action. The only one is `github-actions`. |
| `allow` | none | Destinations the job and its containers may reach. |
| `inspect` | none | Hosts whose TLS the proxy terminates to scan request bodies for secrets. See [Inspection and DLP](https://developers.openwaf.dev/egress/ci/inspect-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

| Rule | Matches |
|---|---|
| `proxy.golang.org` | That name, ports 80 and 443 |
| `*.npmjs.org` | Any name below `npmjs.org` (`registry.npmjs.org`, `a.b.npmjs.org`), not `npmjs.org` itself; ports 80 and 443 |
| `registry.example.com:5000` | That name, port 5000 only |
| `registry.example.com:80,5000` | That name, ports 80 and 5000 |
| `ci-cache.example.com:*` | That name, any port |
| `203.0.113.10` | That 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](https://developers.openwaf.dev/egress/ci/docker/#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`](https://api.github.com/meta): 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

| Input | Default | Meaning |
|---|---|---|
| `policy` | `.github/egress.yml` | Path of the policy file. |
| `mode` | empty | `audit` or `enforce`; overrides `mode` in the file. Useful to try one file in audit on pull requests and enforce it on `main`. |
| `lock` | `auto` | The 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. |
| `containers` | `track` | `track`: 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](https://developers.openwaf.dev/egress/ci/docker/). |
| `no-proxy` | empty | Extra 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-events` | `off` | `on`: send the job's destinations, blocks and detections to your organization's Egress events. See [Send events to OpenWAF](https://developers.openwaf.dev/egress/ci/send-events/). |
| `openwaf-api` | `https://dash.openwaf.dev` | OpenWAF API used by `send-events`. Must be `https://`. |

| Output | Meaning |
|---|---|
| `proxy` | The proxy URL, `http://127.0.0.1:PORT`. |

## What the action sets for the job

| Variable | Value |
|---|---|
| `HTTPS_PROXY`, `HTTP_PROXY`, `https_proxy`, `http_proxy` | The proxy URL. |
| `NO_PROXY`, `no_proxy` | `localhost,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_STATE` | The sidecar's state directory, `$RUNNER_TEMP/egress-ci` (the event log is `events.jsonl` there). |
| `DOCKER_HOST`, `BUILDX_BUILDER` | With 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_CAINFO` | With `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:

| Reason | Why |
|---|---|
| `not_allowed` | The name, or the port for that name, is not in `allow`. |
| `sni_mismatch` | A `CONNECT` to an allowed name carried a TLS SNI for a name outside the policy. |
| `name_ip_mismatch` | A connection named an allowed host but went to an address that name does not resolve to (domain fronting). |
| `dlp` | The request carried a secret (see [Inspection and DLP](https://developers.openwaf.dev/egress/ci/inspect-and-dlp/)). |
| `registry_not_allowed` | An image pull from a registry outside the policy. |
| `private_destination` | Loopback, 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.json | Meaning |
|---|---|
| `meta` | Version, 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. |
| `destinations` | One 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_queries` | Totals. |
| `suggested_allow` | Names outside the policy, ready for `allow`. |
| `image_pulls` | Pulls through the job's socket, with their decision. |
| `other_containers` | Container flows on the runner that are not this job's. |
| `unattributed_addresses` | Non-TCP flows to addresses no DNS answer named. |
