Repository navigation
Add egress control through allowlist to prevent source code exfiltration - #230
Merged
v-abhishekbhaskar merged 8 commits intoSep 10, 2026
Merged
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The configuration contract is inconsistent, per-host metrics aggregate incorrectly, and exact hostname normalization is incomplete.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Balanced
Findings: 3
New issues introduced by this change (4)
| Severity | Finding |
|---|---|
internal/handlers/egress_allowlist.go — The advertised config contract is not implemented here: the PR description says callers enable… |
|
internal/handlers/egress_allowlist.go — This does not produce per-host counts with the real metrics client. CollectorClient.SendMetric… |
|
internal/handlers/egress_allowlist.go — Exact allowlist entries do not receive the trailing-dot normalization provided by… |
|
proxy_test.go — This integration test claims to cover observe-mode logging but only checks that traffic is allowed,… |
What changed in this PR
Adds experiment-gated, domain-based egress monitoring and enforcement to the proxy.
Changes:
- Adds static ecosystem allowlists and boundary-safe domain matching.
- Logs, measures, or blocks non-allowlisted requests.
- Adds unit and MITM integration coverage.
| File | Description |
|---|---|
.gitignore |
Ignores VS Code settings. |
proxy.go |
Registers the egress handler. |
proxy_test.go |
Adds proxy integration tests. |
internal/helpers/helpers.go |
Adds domain-suffix matching. |
internal/helpers/helpers_test.go |
Tests domain matching boundaries. |
internal/handlers/egress_allowlist.go |
Implements egress handling. |
internal/handlers/egress_allowlist_test.go |
Tests handler modes and metrics. |
internal/handlers/egress_allowlist_defaults.go |
Defines ecosystem allowlists. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+87
to
+110
| func TestProxyEgressAllowlistObserveAllows(t *testing.T) { | ||
| upstream := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(http.StatusOK) | ||
| })) | ||
| defer upstream.Close() | ||
|
|
||
| cfg := &config.Config{ | ||
| CA: testProxyConfig.CA, | ||
| Experiments: config.Experiments{"proxy_egress_observe": true}, | ||
| } | ||
| env := config.ProxyEnvSettings{PackageManager: "npm_and_yarn"} | ||
| client, proxy := testProxyServerWithEnv(t, env, cfg, nil, upstream.Certificate()) | ||
| closeOnCleanup(t, proxy) | ||
|
|
||
| // Observe mode logs the non-allowlisted host but still lets it through. | ||
| req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, upstream.URL, nil) | ||
| require.NoError(t, err) | ||
| rsp, err := client.Do(req) | ||
| require.NoError(t, err) | ||
| defer func() { | ||
| require.NoError(t, rsp.Body.Close()) | ||
| }() | ||
| assert.Equal(t, http.StatusOK, rsp.StatusCode) | ||
| } |
v-robaiken
reviewed
Sep 8, 2026
…rring from credentials
honeyankit
force-pushed
the
abhishekbhaskar/add-egress-allowlist-log-only
branch
from
September 9, 2026 23:22
1d05f73 to
ed312a3
Compare
v-robaiken
approved these changes
Sep 10, 2026
v-kbukum1
approved these changes
Sep 10, 2026
v-abhishekbhaskar
deleted the
abhishekbhaskar/add-egress-allowlist-log-only
branch
September 10, 2026 17:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What are you trying to accomplish?
Add domain-based egress control to the proxy to help prevent source-code exfiltration during Dependabot update jobs. Even when an attacker abuses a legitimately-configured registry token (e.g. Artifactory/Nexus), traffic to unknown hosts like
evil.comshould be visible and, eventually, blockable — something the earlier Method-header approach could not address.This PR lands the observe (log-only) foundation plus the per-job dynamic allowlist needed before enforcement can be turned on: it builds a per-job allowlist and logs any request to a host not on it, without changing traffic behavior by default.
Anything you want to highlight for special attention from reviewers?
proxy_egress_observe(log non-allowlisted requests, still allow) andproxy_egress_enforce(drop with 403, matching the existingblockMetadataAPIHostsresponse). Both defaultfalse⇒ fail-open, so existing jobs and GHES are unaffected. Using experiments lets us roll this out gradually without a config-schema change.PACKAGE_MANAGERdependency): GitHub/Dependabot infrastructure domains plus the union of every ecosystem's default registry/CDN hosts, applied to every job. This is deliberate — launchers don't reliably setPACKAGE_MANAGER(the action doesn't; the CLI needed patching), and multi-ecosystem jobs need registries beyond their nominal package manager. All entries are trusted public registries, so the containment trade-off is negligible versus the exfil protection (unknown hosts are still blocked).internal/handlers/egress_allowlist_defaults.yaml,//go:embed) parsed atinit(). The per-ecosystem keyed map is retained there for provenance/documentation; the handler consumes the deduped, sorted union. A&docker_registriesYAML anchor shares the container-registry set acrossdocker,docker_compose,devcontainers, andhelm.cfg.Credentialsat construction — no per-request cost):host,url,index-url,registry,api-host,repo,endpoint,replaces-base), so a job's private registries (Artifactory/Nexus/private npm, etc.) are never treated as exfiltration.login.microsoftonline.com), AWS (sts.amazonaws.com), GCP (sts/iamcredentials/www.googleapis.com), Cloudsmith (api-hostorapi.cloudsmith.io). Detection mirrors the identifying keys inoidc.CreateOIDCCredential; the eventual registry target is already covered by the credential-host extraction.domainsescape hatch: an explicit per-credentialdomainslist is merged verbatim (bare hosts or leading-dot suffix patterns) for redirect/CDN targets the proxy can't derive.helpers.HostMatchesDomainuses a leading-dot suffix check so one entry covers subdomains (.pkg.dev→us-docker.pkg.dev) while lookalikes likeevilnpmjs.orgare rejected. Exact entries and the suffix form both normalize an absolute DNS name (trailing dot), soregistry.npmjs.org.matches.proxy.goright after request logging and before all credential-injecting handlers, so drops/logs happen before any auth is added. Works for HTTP and MITM'd HTTPS.* egress not allowlisted <host>) via the logging pipeline / Kusto.How will you know you've accomplished your goal?
HostMatchesDomainboundary matrix (subdomains, case/trailing-dot normalization, lookalike negatives); handler flag matrix (fail-open, observe-logs-allows, enforce-403, observe+enforce); GitHub-infra always allowed; union covers every ecosystem's defaults regardless ofPACKAGE_MANAGER; absolute-FQDN exact match; embedded-YAML load/dedup guard; collector separates two hosts into distinct series.proxy_test.go): end-to-end through MITM — non-allowlisted host returns 403 underenforce, and 200 + aegress not allowlisted <host>log line underobserve(asserted from captured logger output).go build ./...,go test ./...,gofmt, andyamllintall pass.Rollout plan
domains) covers real jobs — confirm from logs that enforce would not have broken anything.proxy_egress_enforce(already wired).Checklist