From 978f942cc32da7155cb432594eb7511506e02959 Mon Sep 17 00:00:00 2001 From: v-abhishekbhaskar Date: Wed, 30 Sep 2026 13:44:15 -0500 Subject: [PATCH] add some more blocked domains to egress allowlist --- .../handlers/egress_allowlist_defaults.yaml | 27 +++++ internal/handlers/egress_allowlist_test.go | 103 ++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/internal/handlers/egress_allowlist_defaults.yaml b/internal/handlers/egress_allowlist_defaults.yaml index a296e30..4a27a5a 100644 --- a/internal/handlers/egress_allowlist_defaults.yaml +++ b/internal/handlers/egress_allowlist_defaults.yaml @@ -233,6 +233,16 @@ ecosystem_default_domains: - pydantic.dev - psycopg.org - www.psycopg.org + - cryptography.io + - numpy.org + - docs.sentry.io + - coverage.readthedocs.io + - reference.langchain.com + - developer.nvidia.com + # cloud.google.com 301-redirects to docs.cloud.google.com, so both ends of + # the chain are required. + - cloud.google.com + - docs.cloud.google.com uv: *python_registries bundler: - rubygems.org @@ -243,6 +253,8 @@ ecosystem_default_domains: # GitHub Packages RubyGems content host; rubygems.pkg.github.com redirects here. # Exact only; listed in api.github.com/meta domains.packages. - rubygemsregistryv2prod.blob.core.windows.net + # Gem documentation host, read for release notes. Not a registry. + - rubydoc.info maven: &jvm_registries - repo.maven.apache.org - repo1.maven.org @@ -270,6 +282,13 @@ ecosystem_default_domains: # AndroidX release pages. See the note in the pip list above. - commons.apache.org - developer.android.com + # Public vendor Maven repositories, verified anonymous with no redirects. + - nexus.payara.fish + - repository.mulesoft.org + - maven.repository.redhat.com + - artifacts.alfresco.com + - repo.grails.org + - releases.aspose.com # packages.atlassian.com 301-redirects to maven.artifacts.atlassian.com. - packages.atlassian.com - maven.artifacts.atlassian.com @@ -292,6 +311,9 @@ ecosystem_default_domains: - asset-packagist.org - cdn.asset-packagist.org - satis.spatie.be + # Composer repository published via GitHub Pages. Exact only: + # github.io subdomains are user-creatable. + - wp-languages.github.io go_modules: - proxy.golang.org - sum.golang.org @@ -335,6 +357,9 @@ ecosystem_default_domains: # apex entry does NOT match virtual-hosted ".storage.googleapis.com" # subdomains, which stay blocked unless listed exactly (see maven-central). - storage.googleapis.com + # Go package documentation, read when resolving module metadata and + # release notes. Not a module proxy. + - pkg.go.dev docker: &docker_registries - registry-1.docker.io - auth.docker.io @@ -429,6 +454,8 @@ ecosystem_default_domains: - registry.opentofu.org - releases.hashicorp.com - checkpoint-api.hashicorp.com + # OpenTofu's public registry API, serving provider/module metadata. + - api.opentofu.org elm: - package.elm-lang.org deno: diff --git a/internal/handlers/egress_allowlist_test.go b/internal/handlers/egress_allowlist_test.go index 586e9d6..6f7d29a 100644 --- a/internal/handlers/egress_allowlist_test.go +++ b/internal/handlers/egress_allowlist_test.go @@ -1145,3 +1145,106 @@ func TestEgressAllowlist_ChangelogHostsAllowed(t *testing.T) { } } } + +// Public vendor registries surfaced by the 75% enforce rollout. Each was +// verified to serve a real artifact anonymously with no cross-origin redirect. +func TestEgressAllowlist_PublicVendorRegistriesAllowed(t *testing.T) { + h := newEgressHandler(false, true, "") + + for _, allowed := range []string{ + "https://nexus.payara.fish/repository/payara-artifacts/fish/payara/api/payara-bom/maven-metadata.xml", + "https://repository.mulesoft.org/nexus/content/repositories/public/org/mule/mule-core/maven-metadata.xml", + "https://maven.repository.redhat.com/ga/org/jboss/jboss-parent/maven-metadata.xml", + "https://artifacts.alfresco.com/nexus/content/repositories/public/org/alfresco/alfresco-core/maven-metadata.xml", + "https://repo.grails.org/grails/core/org/grails/grails-core/maven-metadata.xml", + "https://releases.aspose.com/java/repo/com/aspose/aspose-words/maven-metadata.xml", + "https://api.opentofu.org/registry/docs/providers/hashicorp/aws/index.json", + "https://wp-languages.github.io/packages.json", + "https://pkg.go.dev/github.com/gorilla/mux", + } { + assert.Nil(t, egressResult(t, h, allowed), "public vendor registry allowed: "+allowed) + } + + for _, blocked := range []string{ + // github.io subdomains are user-creatable, so one Pages repository must + // not expose the namespace or any sibling. + "https://github.io/payload", + "https://evil.github.io/payload", + "https://evil.wp-languages.github.io/payload", + // repo.magento.com returns 401: it needs a registries: credential, so + // allowlisting the public hosts must not quietly cover it. + "https://repo.magento.com/packages.json", + // Multi-tenant hosts whose tenant lives in the path stay blocked, per + // the dl.cloudsmith.io precedent. + "https://packagecloud.io/org/repo/packages", + "https://api.cloudsmith.io/v1/packages/org/repo/", + // Exact entries must not widen to children or lookalike parents. + "https://evil.nexus.payara.fish/payload", + "https://payara.fish/payload", + "https://evil.api.opentofu.org/payload", + "https://opentofu.org/payload", + "https://evil.pkg.go.dev/payload", + "https://repo.grails.org.attacker.com/payload", + // Every exact entry gets a child probe so a later widening to a + // leading-dot suffix cannot pass silently. + "https://evil.repo.grails.org/payload", + "https://grails.org/payload", + "https://evil.repository.mulesoft.org/payload", + "https://evil.maven.repository.redhat.com/payload", + "https://evil.artifacts.alfresco.com/payload", + "https://evil.releases.aspose.com/payload", + } { + resp := egressResult(t, h, blocked) + if assert.NotNil(t, resp, "must stay blocked: "+blocked) { + assert.Equal(t, http.StatusForbidden, resp.StatusCode) + } + } +} + +// Second wave of changelog hosts, from the 75% rollout. Same rationale as +// TestEgressAllowlist_ChangelogHostsAllowed: metadata links, not resolution. +func TestEgressAllowlist_ChangelogHostsSecondWaveAllowed(t *testing.T) { + h := newEgressHandler(false, true, "") + + for _, allowed := range []string{ + "https://cryptography.io/en/latest/changelog/", + "https://numpy.org/doc/stable/release.html", + "https://docs.sentry.io/platforms/python/", + "https://coverage.readthedocs.io/en/latest/changes.html", + "https://reference.langchain.com/python/", + "https://developer.nvidia.com/cuda-toolkit", + "https://rubydoc.info/gems/rails", + // cloud.google.com 301s to docs.cloud.google.com; both ends required. + "https://cloud.google.com/python/docs/reference", + "https://docs.cloud.google.com/python/docs/reference", + } { + assert.Nil(t, egressResult(t, h, allowed), "changelog host allowed: "+allowed) + } + + for _, blocked := range []string{ + // The Google Cloud web console is a sign-in flow, not a registry, and + // allowing cloud.google.com must not reach it. + "https://console.cloud.google.com/", + // A project_urls community link, deliberately not allowlisted. + "https://www.reddit.com/r/python/", + // readthedocs.io stays exact-only: subdomains are project-creatable. + "https://evil.readthedocs.io/payload", + "https://evil.coverage.readthedocs.io/payload", + // Exact entries must not widen. + "https://evil.numpy.org/payload", + "https://evil.cloud.google.com/payload", + "https://google.com/payload", + "https://cryptography.io.attacker.com/payload", + "https://evil.cryptography.io/payload", + "https://evil.docs.sentry.io/payload", + "https://evil.reference.langchain.com/payload", + "https://evil.developer.nvidia.com/payload", + "https://evil.rubydoc.info/payload", + "https://evil.docs.cloud.google.com/payload", + } { + resp := egressResult(t, h, blocked) + if assert.NotNil(t, resp, "must stay blocked: "+blocked) { + assert.Equal(t, http.StatusForbidden, resp.StatusCode) + } + } +}