Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Preserve threat detection network alias
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
  • Loading branch information
Copilot and gh-aw-bot authored Aug 24, 2026
commit 03a3bcb1a697f06ca59418ca84c2b02bdba7b99d
7 changes: 5 additions & 2 deletions .github/aw/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ Keywords expanding to curated domain lists:
| `lean` | Lean theorem prover | `lean-lang.org`, `elan.lean-lang.org`, `reservoir.lean-lang.org` |
| `python-native` | Python native build deps | Native toolchain mirrors for building Python packages from source |
| `copilot-vendor` | Copilot plan-specific APIs / telemetry | `api.business.githubcopilot.com`, `api.enterprise.githubcopilot.com`, `api.individual.githubcopilot.com`, `telemetry.enterprise.githubcopilot.com` |
| `threat-detection` | Compatibility alias for Copilot threat detection | Copilot API/telemetry hosts, GitHub API/web, `host.docker.internal`, `registry.npmjs.org` |

## Automatic Engine Domain Sets

Each engine automatically receives the domain set it requires in addition to
`network.allowed`. These named sets are maintained by the compiler for analysis
and reporting; they are not valid `network.allowed` identifiers.
and reporting; they are not valid `network.allowed` identifiers, except for
the legacy `threat-detection` compatibility alias listed above.

| Engine set | Included domains |
|---|---|
Expand All @@ -100,7 +102,8 @@ and reporting; they are not valid `network.allowed` identifiers.
| `codex` | `172.30.0.1`, `api.github.com`, `api.openai.com`, `chatgpt.com`, `github.com`, `host.docker.internal`, `openai.com` |
| `gemini` | `*.googleapis.com`, `generativelanguage.googleapis.com`, `github.com`, `host.docker.internal`, `raw.githubusercontent.com` |
| `pi` | `api.githubcopilot.com`, `github.com`, `host.docker.internal`, `raw.githubusercontent.com`; provider-scoped models replace the API host with the selected provider endpoint |
| `threat-detection` | Copilot API and telemetry hosts, `api.github.com`, `github.com`, `host.docker.internal`, and `registry.npmjs.org` for read-only lockfile validation |
| `pi-base` | `github.com`, `host.docker.internal`, `raw.githubusercontent.com`; applied as the provider-independent baseline before a provider prefix is resolved |
| `threat-detection` | Applied automatically only to Copilot threat-detection runs: Copilot API and telemetry hosts, `api.github.com`, `github.com`, `host.docker.internal`, and `registry.npmjs.org` for read-only lockfile validation. External Claude, Codex, Gemini, and other detection runs use their own engine defaults. |

## Invalid Shorthands

Expand Down
7 changes: 5 additions & 2 deletions docs/src/content/docs/reference/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Mix ecosystem identifiers with specific domains for fine-grained control:
| `bazel` | Bazel build system (`releases.bazel.build`, `bcr.bazel.build`) |
| `clojure` | Clojure packages (`clojars.org`) |
| `copilot-vendor` | Plan-specific Copilot API hosts (`api.business.githubcopilot.com`, `api.enterprise.githubcopilot.com`, `api.individual.githubcopilot.com`) and Copilot telemetry (`telemetry.enterprise.githubcopilot.com`) — not enabled by default, since agents route inference through the firewall gateway |
| `threat-detection` | Compatibility alias for Copilot threat-detection network access (`api.githubcopilot.com`, Copilot plan APIs, telemetry, `api.github.com`, `github.com`, `host.docker.internal`, `registry.npmjs.org`) |
| `dart` | Dart/Flutter packages (`pub.dev`, `storage.googleapis.com`) |
| `deno` | Deno runtime (`deno.land`, `jsr.io`, `googleapis.deno.dev`) |
| `dotnet` | NuGet packages and .NET SDK |
Expand Down Expand Up @@ -120,7 +121,8 @@ Mix ecosystem identifiers with specific domains for fine-grained control:

Each engine automatically receives the domain set it requires in addition to
`network.allowed`. These named sets are maintained by the compiler for analysis
and reporting; they are not valid `network.allowed` identifiers.
and reporting; they are not valid `network.allowed` identifiers, except for
the legacy `threat-detection` compatibility alias listed above.

| Engine set | Included domains |
|---|---|
Expand All @@ -129,7 +131,8 @@ and reporting; they are not valid `network.allowed` identifiers.
| `codex` | `172.30.0.1`, `api.github.com`, `api.openai.com`, `chatgpt.com`, `github.com`, `host.docker.internal`, `openai.com` |
| `gemini` | `*.googleapis.com`, `generativelanguage.googleapis.com`, `github.com`, `host.docker.internal`, `raw.githubusercontent.com` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/grill-with-docs] The pi-base set exists in engineDefaultDomainSets and is exported via GetEngineDefaultDomainSets() / PiBaseDefaultDomains, but it is absent from both doc tables — leaving a gap between the code and the reference docs.

💡 Suggested addition

Add a row after the pi row in both docs/src/content/docs/reference/network.md and .github/aw/network.md:

| `pi-base` | `host.docker.internal`, `github.com`, `raw.githubusercontent.com`; applied as the provider-independent baseline before a provider prefix is resolved |

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 03a3bcb: added the missing pi-base row to both network reference tables.

| `pi` | `api.githubcopilot.com`, `github.com`, `host.docker.internal`, `raw.githubusercontent.com`; provider-scoped models replace the API host with the selected provider endpoint |
| `threat-detection` | Copilot API and telemetry hosts, `api.github.com`, `github.com`, `host.docker.internal`, and `registry.npmjs.org` for read-only lockfile validation |
| `pi-base` | `github.com`, `host.docker.internal`, `raw.githubusercontent.com`; applied as the provider-independent baseline before a provider prefix is resolved |
| `threat-detection` | Applied automatically only to Copilot threat-detection runs: Copilot API and telemetry hosts, `api.github.com`, `github.com`, `host.docker.internal`, and `registry.npmjs.org` for read-only lockfile validation. External Claude, Codex, Gemini, and other detection runs use their own engine defaults. |

### Ecosystem Identifier Validation

Expand Down
11 changes: 11 additions & 0 deletions pkg/workflow/data/ecosystem_domains.json
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@
"scala": ["repo.scala-sbt.org", "scala-ci.typesafe.com", "repo.typesafe.com", "jitpack.io", "dl.bintray.com", "scala.jfrog.io"],
"swift": ["download.swift.org", "swift.org", "cocoapods.org", "cdn.cocoapods.org"],
"terraform": ["releases.hashicorp.com", "apt.releases.hashicorp.com", "yum.releases.hashicorp.com", "registry.terraform.io"],
"threat-detection": [
"api.business.githubcopilot.com",
"api.enterprise.githubcopilot.com",
"api.github.com",
"api.githubcopilot.com",
"api.individual.githubcopilot.com",
"github.com",
"host.docker.internal",
"registry.npmjs.org",
"telemetry.enterprise.githubcopilot.com"
],
"zig": ["ziglang.org", "pkg.machengine.org", "deps.files.ghostty.org"]
},
"engine-defaults": {
Expand Down
26 changes: 15 additions & 11 deletions pkg/workflow/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func getLoadedEcosystemDomains() map[string][]string {
return getLoadedDomainSets().Ecosystems
}

// Engine default domain lists intentionally exclude package registries (npm, PyPI, ...).
// Runtime engine default domain lists intentionally exclude package registries (npm, PyPI, ...).
//
// Engine CLIs and SDKs are installed by dedicated GitHub Actions steps that run on the
// runner *before* the AWF-wrapped agent step, so package registries are not needed inside
Expand All @@ -73,21 +73,25 @@ func getLoadedEcosystemDomains() map[string][]string {
// documented behavior that package ecosystems require explicit opt-in
// (`network: { allowed: [node] }`, `[python]`, or a matching `runtimes:` entry).
//
// This invariant is enforced by TestEngineDefaultDomainsDoNotOverlapEcosystems in
// domains_package_registry_test.go, which fails if any engine default domain list below
// overlaps with the full "node" or "python" ecosystem domain sets in data/ecosystem_domains.json
// — not just the registries known when this comment was written. If you need to add a domain
// to an engine default and that test starts failing, the domain belongs behind an explicit
// ecosystem/runtime opt-in instead, not in the unconditional default list.
// This invariant is enforced for runtime engine defaults by
// TestEngineDefaultDomainsDoNotOverlapEcosystems in domains_package_registry_test.go, which
// fails if those default domain lists overlap with the full "node" or "python" ecosystem
// domain sets in data/ecosystem_domains.json — not just the registries known when this comment
// was written. If you need to add a package-registry domain to a runtime engine default and
// that test starts failing, the domain belongs behind an explicit ecosystem/runtime opt-in
// instead, not in the unconditional default list. Copilot threat detection is the exception:
// its dedicated detection allow-list includes registry.npmjs.org for read-only lockfile
// validation, and that list is not part of normal agent engine defaults.

// engineDefaultDomainSets centralizes every unconditional engine allow-list. These
// sets are intentionally distinct from user-selectable ecosystem domain lists: the
// compiler includes them automatically for the matching engine, rather than allowing
// users to select them through network.allowed.
//
// Engine default domain lists intentionally exclude package registries (npm, PyPI,
// and similar). The threat-detection set is the exception because the Copilot
// threat detector uses the npm registry. See the package-registry invariant above.
// Runtime engine default domain lists intentionally exclude package registries
// (npm, PyPI, and similar). The threat-detection set is the exception because
// Copilot threat detection uses the npm registry. See the package-registry
// invariant above.
var engineDefaultDomainSets = getLoadedDomainSets().EngineDefaults

// GetEngineDefaultDomainSets returns copies of the named engine domain sets for
Expand Down Expand Up @@ -707,7 +711,7 @@ func GetAllowedDomainsForEngine(engine constants.EngineName, network *NetworkPer
// Any additional user-specified network.allowed entries are merged in (typically empty for detection).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] Direct map access hands the internal slice to mergeDomainsWithNetworkToolsAndRuntimes — if that function appends to the slice in a future grow-in-place scenario, it could silently corrupt the registry entry for subsequent calls.

💡 Suggested fix

Use the same defensive copy pattern used everywhere else in this PR:

detectionDomains := copyEngineDefaultDomainSet(engineDefaultDomainSets["threat-detection"])

This is a one-liner and keeps the access pattern consistent with CopilotDefaultDomains, ClaudeDefaultDomains, etc.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 03a3bcb: GetThreatDetectionAllowedDomains now copies the threat-detection registry slice before merging additional network entries.

// Returns a deduplicated, sorted, comma-separated string suitable for AWF's --allow-domains flag.
func GetThreatDetectionAllowedDomains(network *NetworkPermissions) string {
detectionDomains := engineDefaultDomainSets["threat-detection"]
detectionDomains := copyEngineDefaultDomainSet(engineDefaultDomainSets["threat-detection"])
// Pass nil tools and runtimes: detection runs with no npm/runtime ecosystem, so
// ecosystem domain expansion is intentionally skipped.
return mergeDomainsWithNetworkToolsAndRuntimes(detectionDomains, network, nil, nil)
Expand Down
18 changes: 14 additions & 4 deletions pkg/workflow/domains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,20 +429,28 @@ func TestThreatDetectionDomains(t *testing.T) {
detectionMap[d] = true
}
for _, required := range requiredDomains {
assert.True(t, detectionMap[required], "Required domain %q not found in threat-detection ecosystem", required)
assert.True(t, detectionMap[required], "Required domain %q not found in threat-detection domain set", required)
}

// Detection domains must NOT include the domains excluded for supply-chain reduction
excludedDomains := []string{
"raw.githubusercontent.com",
}
for _, excluded := range excludedDomains {
assert.False(t, detectionMap[excluded], "Domain %q should not be in threat-detection ecosystem (excluded to reduce supply chain surface)", excluded)
assert.False(t, detectionMap[excluded], "Domain %q should not be in threat-detection domain set (excluded to reduce supply chain surface)", excluded)
}

// Verify exact count — no silent additions
assert.Len(t, detectionDomains, len(requiredDomains),
"threat-detection ecosystem should have exactly %d entries", len(requiredDomains))
"threat-detection domain set should have exactly %d entries", len(requiredDomains))
}

func TestThreatDetectionNetworkAllowedCompatibilityAlias(t *testing.T) {
engineDefaults := GetEngineDefaultDomainSets()["threat-detection"]
expanded := GetAllowedDomains(&NetworkPermissions{Allowed: []string{"threat-detection"}})

assert.Equal(t, engineDefaults, expanded,
"legacy network.allowed threat-detection alias must expand to the Copilot detection domain set")
}

func TestGetEngineDefaultDomainSets(t *testing.T) {
Expand Down Expand Up @@ -473,10 +481,12 @@ func TestEmbeddedDomainSets(t *testing.T) {
sets := getLoadedDomainSets()

assert.Contains(t, sets.Ecosystems, "defaults")
assert.Contains(t, sets.Ecosystems, "threat-detection")
assert.Contains(t, sets.EngineDefaults, "copilot")
assert.Equal(t, "api.githubcopilot.com", sets.PiProviderDomains["copilot"])
assert.Equal(t, []string{"github.com", "localhost"}, sets.SanitizationDefaults)
assert.NotContains(t, sets.Ecosystems, "threat-detection")
assert.Equal(t, sets.EngineDefaults["threat-detection"], sets.Ecosystems["threat-detection"],
"legacy network.allowed threat-detection alias must stay in sync with Copilot detection defaults")
}

func TestGetThreatDetectionAllowedDomains(t *testing.T) {
Expand Down
Loading