Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions internal/handlers/egress_allowlist_defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ ecosystem_default_domains:
# the chain are required.
- cloud.google.com
- docs.cloud.google.com
- click.palletsprojects.com
- pytest-mock.readthedocs.io
- redis.readthedocs.io
uv: *python_registries
bundler:
- rubygems.org
Expand Down Expand Up @@ -292,6 +295,30 @@ ecosystem_default_domains:
# packages.atlassian.com 301-redirects to maven.artifacts.atlassian.com.
- packages.atlassian.com
- maven.artifacts.atlassian.com
# Public vendor/community Maven repositories, verified anonymous: each
# returns 404 (not 401) for an absent artifact, so no auth is demanded.
- repo.opencollab.dev
- maven.restlet.talend.com
- maven.fpregistry.io
- repo.essentialsx.net
- repo.dmulloy2.net
- api.xposed.info
- packages.nuxeo.com
- maven.fullstory.com
- maven.lokalise.com
- repository.medallia.com
- jogamp.org
# JCenter is retired but still 301-redirects to repo1.maven.org, which is
# already allowed; only the entry point of the chain needs listing.
- jcenter.bintray.com
# Exact only. GitHub Pages hosts this vendor's Maven repo, but "*.github.io"
# is user-creatable, so a leading-dot entry would be an exfiltration channel.
- salesforce-marketingcloud.github.io
# Exact virtual-hosted S3 bucket (Automattic's public Android libraries).
# Never the path-style "s3.amazonaws.com" apex, which reaches every bucket.
- a8c-libs.s3.amazonaws.com
# Changelog/release-note host for Log4j and friends, not a registry.
- logging.apache.org
gradle: *jvm_registries
sbt:
- repo1.maven.org
Expand Down Expand Up @@ -418,6 +445,8 @@ ecosystem_default_domains:
# possible exfiltration destination. They are retained only
# because NuGet restores rely on these rotating CDN hosts.
- "*vsblobprod*.blob.core.windows.net"
# Public community NuGet feed, serving an anonymous v3 service index.
- pkg.kzu.app
- "*.vsblob.vsassets.io"
# Certificate revocation endpoints. NuGet validates author/repository
# signatures on restore, so blocking these stalls or fails verification of
Expand Down Expand Up @@ -450,6 +479,8 @@ ecosystem_default_domains:
# Terraform CLI version check. Alternative: set CHECKPOINT_DISABLE=1 in the
# updater image and leave this blocked.
- checkpoint-api.hashicorp.com
# Module archive downloads from the public registry.
- archivist.terraform.io
opentofu:
- registry.opentofu.org
- releases.hashicorp.com
Expand All @@ -471,6 +502,16 @@ ecosystem_default_domains:
- us-west.pkg.julialang.org
# Official Julia release/artifact storage.
- julialang-s3.julialang.org
# storage.julialang.net 302-redirects package tarballs to the exact
# virtual-hosted bucket below, so both ends of the chain are required.
#
# The bucket is pinned exactly rather than globbed over the region: only
# "julialang-storage-us-east-1" exists today, and sibling names such as
# "julialang-storage-eu-west-1" are unclaimed, so a
# "julialang-storage-*.s3.*.amazonaws.com" pattern would point at an
# attacker-registrable bucket. If Julia adds a region, add it here.
- storage.julialang.net
- julialang-storage-us-east-1.s3.us-east-1.amazonaws.com
rust_toolchain:
- static.rust-lang.org
conda:
Expand Down
133 changes: 133 additions & 0 deletions internal/handlers/egress_allowlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1248,3 +1248,136 @@ func TestEgressAllowlist_ChangelogHostsSecondWaveAllowed(t *testing.T) {
}
}
}

func TestEgressAllowlist_PublicRegistriesThirdWaveAllowed(t *testing.T) {
h := newEgressHandler(false, true, "")

for _, allowed := range []string{
"https://repo.opencollab.dev/maven-releases/org/geysermc/geyser/maven-metadata.xml",
"https://maven.restlet.talend.com/org/restlet/jse/org.restlet/maven-metadata.xml",
"https://maven.fpregistry.io/releases/io/fairyproject/maven-metadata.xml",
"https://repo.essentialsx.net/releases/net/essentialsx/EssentialsX/maven-metadata.xml",
"https://repo.dmulloy2.net/repository/public/com/comphenix/protocol/ProtocolLib/maven-metadata.xml",
"https://api.xposed.info/api/de/robv/android/xposed/api/maven-metadata.xml",
"https://packages.nuxeo.com/repository/maven-public/org/nuxeo/maven-metadata.xml",
"https://maven.fullstory.com/com/fullstory/gradle-plugin/maven-metadata.xml",
"https://maven.lokalise.com/com/lokalise/sdk/maven-metadata.xml",
"https://repository.medallia.com/artifactory/public/com/medallia/maven-metadata.xml",
"https://jogamp.org/deployment/maven/org/jogamp/gluegen/maven-metadata.xml",
"https://jcenter.bintray.com/com/google/guava/guava/maven-metadata.xml",
"https://salesforce-marketingcloud.github.io/MarketingCloudSDK-Android/maven-metadata.xml",
"https://a8c-libs.s3.amazonaws.com/android/com/automattic/maven-metadata.xml",
"https://pkg.kzu.app/index.json",
"https://archivist.terraform.io/v1/object/abc123",
"https://storage.julialang.net/registries/23338594-aafe-5451-b93e-139f81909106",
"https://julialang-storage-us-east-1.s3.us-east-1.amazonaws.com/package/abc",
} {
assert.Nil(t, egressResult(t, h, allowed), "public registry allowed: "+allowed)
}

for _, blocked := range []string{
// Path-style shared object storage must never be allowlisted: the apex
// reaches every bucket, so only the exact virtual-hosted bucket is listed.
"https://s3.amazonaws.com/attacker-bucket/payload",
"https://s3.us-east-1.amazonaws.com/attacker-bucket/payload",
"https://s3-us-west-2.amazonaws.com/attacker-bucket/payload",
// Sibling Julia bucket names are unclaimed and therefore registrable, so
// the region must never be globbed.
"https://julialang-storage-eu-west-1.s3.eu-west-1.amazonaws.com/payload",
"https://julialang-storage-evil.s3.us-east-1.amazonaws.com/payload",
// github.io subdomains are user-creatable; one Pages repo must not
// expose the namespace or any sibling.
"https://evil.salesforce-marketingcloud.github.io/payload",
// Hosts that return 401 need a registries: credential. Allowlisting the
// public set must not quietly cover them.
"https://mobile-sdks.forter.com/android/maven-metadata.xml",
"https://nuget.devexpress.com/api/v3/index.json",
// Commercial feeds tenanted by a license key in the path.
"https://nuget.hangfire.io/pro/v3/index.json",
"https://nuget.abp.io/key/v3/index.json",
"https://registry.nes.herodevs.com/angular/core",
"https://connect.advancedcustomfields.com/v1/plugins/download",
// Application Insights telemetry ingestion, not a registry.
"https://dc.services.visualstudio.com/v2/track",
// Retired Bintray download host; only the JCenter redirector is listed.
"https://dl.bintray.com/payload",
// Every exact entry gets a child probe so a later widening to a
// leading-dot suffix cannot pass silently.
Comment thread
v-abhishekbhaskar marked this conversation as resolved.
"https://evil.repo.opencollab.dev/payload",
"https://evil.maven.restlet.talend.com/payload",
"https://evil.maven.fpregistry.io/payload",
"https://evil.repo.essentialsx.net/payload",
"https://evil.repo.dmulloy2.net/payload",
"https://evil.api.xposed.info/payload",
"https://evil.packages.nuxeo.com/payload",
"https://evil.maven.fullstory.com/payload",
"https://evil.maven.lokalise.com/payload",
"https://evil.repository.medallia.com/payload",
"https://evil.jogamp.org/payload",
"https://evil.jcenter.bintray.com/payload",
"https://evil.pkg.kzu.app/payload",
"https://evil.archivist.terraform.io/payload",
"https://evil.storage.julialang.net/payload",
// The two exact S3 entries need child probes of their own: the sibling
// and apex probes above catch a glob or a path-style widening, but only
// these catch the entry being changed to a leading-dot suffix.
"https://evil.a8c-libs.s3.amazonaws.com/payload",
"https://evil.julialang-storage-us-east-1.s3.us-east-1.amazonaws.com/payload",
// Lookalike parents and suffix-appending attacker domains.
"https://bintray.com/payload",
"https://talend.com/payload",
"https://nuxeo.com/payload",
"https://fullstory.com/payload",
"https://lokalise.com/payload",
"https://medallia.com/payload",
"https://essentialsx.net/payload",
"https://xposed.info/payload",
"https://julialang.net/payload",
"https://archivist.terraform.io.attacker.com/payload",
} {
resp := egressResult(t, h, blocked)
if assert.NotNil(t, resp, "must stay blocked: "+blocked) {
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
}
}
}

func TestEgressAllowlist_ChangelogHostsThirdWaveAllowed(t *testing.T) {
h := newEgressHandler(false, true, "")

for _, allowed := range []string{
"https://click.palletsprojects.com/en/stable/changes/",
"https://pytest-mock.readthedocs.io/en/latest/changelog.html",
"https://redis.readthedocs.io/en/stable/",
"https://logging.apache.org/log4j/2.x/release-notes.html",
} {
assert.Nil(t, egressResult(t, h, allowed), "changelog host allowed: "+allowed)
}

for _, blocked := range []string{
// readthedocs.io subdomains are project-creatable, so each entry stays
// exact and the namespace itself must never resolve.
"https://readthedocs.io/payload",
"https://evil.readthedocs.io/payload",
"https://evil.pytest-mock.readthedocs.io/payload",
"https://evil.redis.readthedocs.io/payload",
// Child probes guard against a later widening to a leading-dot suffix.
"https://evil.click.palletsprojects.com/payload",
"https://evil.logging.apache.org/payload",
// Lookalike parents and suffix-appending attacker domains.
"https://palletsprojects.com/payload",
"https://logging.apache.org.attacker.com/payload",
// Issue trackers and code browsers carry no renderable changelog.
"https://issues.apache.org/jira/browse/LOG4J2-1",
"https://cs.android.com/android/platform/superproject",
"https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/home.html",
// Chat and link-aggregator hosts reached via project_urls metadata.
"https://gitter.im/org/room",
"https://www.reddit.com/r/python/",
} {
resp := egressResult(t, h, blocked)
if assert.NotNil(t, resp, "must stay blocked: "+blocked) {
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
}
}
}
Loading