Egress CI scans what a job sends for secrets with the data-leak detectors of the OpenWAF Egress gateway. In enforce a request that carries one is refused; in audit it passes and the summary lists the detection.
What is detected
| Detection id | What |
|---|---|
dlp.private_key | A PEM private key (-----BEGIN … PRIVATE KEY-----) |
dlp.aws_access_key | An AWS access key id |
dlp.openai_key | An OpenAI-style sk- key |
dlp.github_token | A GitHub token, except when it goes to GitHub (github.com, githubusercontent.com, ghcr.io, githubapp.com and their subdomains), where the job's own token is expected |
dlp.credit_card | A payment card number that passes the Luhn check |
dns.high_entropy_label | A DNS label of 40 characters or more with high entropy: data encoded into a name |
dns.long_name | A name of 150 characters or more |
tls.sni_mismatch | A CONNECT whose TLS SNI names a host outside the policy |
The dns. detections are flagged, not refused by themselves: the name is still answered or refused by the policy. A secret inside a DNS name or a CONNECT host (dlp.… on the name) is detected on every destination, because the name is visible without decrypting anything.
What is scanned
| Request | Scanned |
|---|---|
Plain HTTP through HTTPS_PROXY/HTTP_PROXY | URL, headers and body |
| Plain HTTP caught by the lock (a tool that ignores the proxy) | The first bytes of the connection: request line, headers and the start of the body |
HTTPS to a host under inspect, through HTTPS_PROXY | URL, headers and body of every request |
| Any other HTTPS | Only the name (CONNECT host, SNI, DNS name), not the content |
Bodies are scanned whole up to 4 MiB. Larger bodies, and binary ones (application/octet-stream, image/*, video/*, zip, gzip, tar), pass unscanned, and the event says so.
inspect
allow:
- api.staging.example.com
inspect:
- api.staging.example.comFor a host under inspect, the proxy terminates the job's TLS with a certificate issued by a CA created for the run, scans each request, and sends it on over a new TLS connection to the real host, verifying that host's certificate as usual. Inspection works on HTTP/1.1 (the proxy offers only http/1.1 in ALPN) and on connections made through HTTPS_PROXY; a tool that ignores the proxy variables reaches the host through the transparent proxy, which checks the name but does not decrypt.
So that the job trusts the run's CA, the action writes its certificate to $RUNNER_TEMP/egress-ci/ca.pem and a bundle with the system CAs, and sets:
| Variable | Read by |
|---|---|
NODE_EXTRA_CA_CERTS | Node.js |
SSL_CERT_FILE | OpenSSL-based tools, Go, Ruby, Python's ssl |
REQUESTS_CA_BUNDLE | Python Requests |
CURL_CA_BUNDLE | curl |
GIT_SSL_CAINFO | Git |
The TLS trade-off
Inspecting means the job accepts a CA that can sign a certificate for any name, for as long as the run lasts. The action limits what that costs:
- The CA is created at the start of each run, valid for 12 hours, and only for that run. Its private key never leaves the sidecar's memory; only the certificate is written.
- It signs certificates only for the hosts under
inspect; every other HTTPS connection stays an opaque tunnel. - The real host's certificate is still verified by the sidecar, so inspecting does not hide a bad certificate from you: the request fails.
And what it does not solve:
- Tools with their own trust store (a JVM's
cacerts, some language package managers) or with certificate pinning fail on inspected hosts. Leave those hosts out ofinspect. - Containers do not get the variables; inspection is for the job's own steps.
- Without
inspect, the content of HTTPS requests is never seen. A secret sent over HTTPS to an allowed host is not detected: the allowlist is the main control, DLP is a second line for plain HTTP and for the hosts you choose to inspect.
Inspect the hosts where a secret in a body would matter and that your tools reach through the proxy: a staging API your tests post to, a webhook, an artifact or telemetry endpoint. Do not inspect registries or GitHub.