feat: run dynamic agent enclaves with repository-scoped GitHub MCP identities - #8276
Conversation
Implements the remaining AWF runtime path for dynamic, GitHub-MCP-backed agent enclaves (ADR 0001, #8195). A dynamic entry now selects one canonical repository at runtime, receives one short-lived `github-repository-read-v1` identity minted through mcpg's private delegation control channel, and reads that repository through GitHub MCP without cloning or mounting a seed. Startup and private handoff: - Validate the compiler-issued control endpoint strictly: literal loopback hosts only (`localhost` is rejected as resolver-dependent), the exact mcpg control path, no credentials/query/fragment, and normalized default-port semantics so `:80` and an omitted port both resolve to 80. - Take custody of the endpoint and capability before any inherited environment is assembled, stage them into the 0700 private root with exclusive 0600 files, and never mount either into the broker, the executor, the model sidecar, the general MCP route, or the delegated data plane. - A dynamic-only entry needs no GH_TOKEN/GITHUB_TOKEN, clones nothing, writes no seed catalog, and mounts neither /awf/seed nor a seed map. Static and separate static+dynamic behaviour is unchanged. Control plane: - Add a strict mcpg v0.4.17 client for create-or-confirm, status, reconcile, revoke, and revoke-by-labels. Operation paths are siblings of the controller name, `requested_ttl` is an integer number of Go nanoseconds, bodies are bounded, and every response is validated against the request before it is trusted. - Recovery calls status, revokes stale labelled identities, then reconciles; admissions stay blocked until that sequence succeeds. Broker integration: - Construct one DynamicRepositoryRegistry per dynamic entry and route enclave_run_agent through canonical admission before content exposure. Because mcpg's control listener is published on host loopback only, the control client runs in the AWF host process and the broker reaches it over an AWF-private request/response directory bind-mounted only into the broker. - Reserve and settle repository, invocation, output-byte, and execution-second quotas on every terminal path; register admissions in the same live per-repository disclosure ledger; revoke on success, agent failure, schema failure, timeout, cancellation, broker error, and shutdown. An unresolved revocation blocks further admissions and downgrades the invocation to the canonical error. Executor: - Generate an invocation-private, bearer-only GitHub MCP configuration limited to list_issues and issue_read for the one admitted repository, with no job token, control capability, handle, envelope, state, generation, or control endpoint, and no route to the control listener. - Update the dynamic agent instructions away from /awf/seed and prohibit cloning, arbitrary URLs, the GitHub CLI, writes, unscoped search, organization/global discovery, and sibling-repository access. The optional admitted SHA is omitted rather than obtained through a broader token or tool, so reads are audited as live. Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
An internal error escaping the executor pipeline after canonical admission could leave a delegated identity live until the shutdown label sweep. Settle and revoke from a finally block keyed by the invocations that actually reached admission, so every terminal path — including an unexpected throw — releases the reservation and revokes the identity exactly once. Also stop the admission channel loop in the test-only reset helper so a leftover interval cannot outlive a suite. Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Three defects each made a dynamic run non-functional, and each was invisible to the suite because the tests entered the pipeline below the broken seam. - runMainWorkflow validated the enclave configuration before prepareEnclaves had taken custody of the delegation handoff, so every dynamic run aborted at startup with the missing-handoff error. Taking custody deletes both environment variables, so exactly one caller may read them; the early gate now validates structure only and defers the handoff check to that single custodian, which still runs before any container is created. - The per-invocation delegation binding was passed to the runner in a config object that runEnclaveContainer never read, so every executor launched with AWF_ENCLAVE_AGENT_DYNAMIC_REPO=undefined and died with a configuration error. The binding now flows through the spec builder, which revalidates the admitted repository and read mode; only those two scalars may vary per invocation, and the reconciliation paths skip the launch-only checks. - The broker's per-launch network-isolation proof only admitted the shared gateway when the static GitHub profile was enabled, so it rejected the very topology a dynamic run creates. It now admits the gateway for either shape, and the broker receives the real gateway container name for a dynamic entry. Adds regression coverage for all three at the layer that was bypassed: runMainWorkflow with a dynamic entry, createAgentRunner through the real runner, and the dynamic steady-state topology. Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
✅ Coverage Check PassedOverall Coverage
📁 Per-file Coverage Changes (10 files)
✨ New Files (6 files)
Coverage comparison generated by |
There was a problem hiding this comment.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Note
This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.
The AWF-private admission channel is a bind mount, so on an ARC/DinD runner the Docker daemon must see it at the same prefixed path the runner wrote it to. If that ever diverged, the broker would poll an empty directory and every dynamic admission would time out into the canonical denial — a fail-closed outcome, but an opaque one. The translation already happens (the channel is part of the server volume list that applyHostPathPrefixToVolumes rewrites); this pins it, alongside the work mount, so the channel carries no special-case risk relative to the paths that already work under DinD. Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
✅ Copilot review passed with no inline comments. @lpcox Add the |
The prose asserted that neither the primary agent, the broker, the executor, nor the model sidecar "can route to" mcpg's delegation control listener. That claim was inherited from gh-aw's own source comment and describes the host publication, not container-to-container reachability. Docker's -p 127.0.0.1 publication only bounds who can reach the port *through the host*. Under network isolation gh-aw binds the in-container listener to 0.0.0.0 — it has to, because Docker NATs a published port to the container's bridge IP and a container-local 127.0.0.1 bind would be unreachable. A peer that shares a Docker network with mcpg addresses the container IP directly and never traverses the published port, so co-attachment rather than publication scope decides reachability. The single-use executor meets mcpg at 172.31.0.40 on the enclave agent network, so it is not held off the control plane by publication. Nothing about the implementation changes: the control plane was always protected by the AWF-only capability, which is never placed in any container's environment or mount and which mcpg checks on every request. The correction is to state that authentication is the enforced control, and to stop implying a routing guarantee this repository does not establish. Also documents the network the primary agent actually uses to reach mcpg (awf-net, via topologyAttach). Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The prose said the broker is confined to one network and mcpg is co-attached with every peer it serves, but nothing showed it. That asymmetry is the whole reason the control plane relies on capability authentication rather than unreachability, and it is the detail an earlier revision of this branch got wrong, so it is worth making visible rather than leaving in a paragraph. Adds a mermaid diagram to the dynamic runtime topology section showing each component and the networks it is homed on, including mcpg's four planes and its image versus container name. Mermaid matches the existing convention in docs/gvisor-integration.md and docs/sbx-integration.md and stays diffable; the syntax was validated by parsing it with the mermaid package rather than assumed. Refs #8195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The two words are used throughout and mean different things, but nothing said
so. An executor is broker-side machinery and a config kind — the code under
containers/enclave/{script,agent}-executor/ ships into the enclave-mcp-server
image and holds the Docker socket. An enclave is the ephemeral container an
executor launches per invocation, with its own image, name, and entrypoint.
One executor launches many enclaves; the executor is trusted and the enclave
is not, so conflating them inverts the trust boundary.
Adds a terminology note rather than renaming anything. "single-use executor"
for the sandbox is pre-existing prose from #8196 and appears on main, so a
partial rename would leave the docs inconsistent with the contract language,
and executor_bearer is mcpg's wire field name. The note explains the overload
instead. The mermaid node, which this PR introduced, is relabelled to name the
container an enclave.
Refs #8195
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
❌ Smoke Gemini reports failed. Facets need polishing...
|
|
❌ Smoke Claude failed Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "api.anthropic.com"See Network Configuration for more information.
|
|
📰 DEVELOPING STORY: Smoke Docker Sbx reports failed. Our correspondents are investigating the incident...
|
|
✅ Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓
|
|
✅ Build Test Suite completed successfully!
|
|
📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅
|
|
🛡️ Smoke Copilot Network Isolation confirmed the egress allowlist is enforced. ✅ Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "example.com"See Network Configuration for more information.
|
|
✅ Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓
|
|
✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟 Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "msfeed25.pkgs.visualstudio.com"See Network Configuration for more information.
|
|
📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤
|
|
✅ Smoke Copilot BYOK AOAI (Entra) completed. Copilot AOAI BYOK (Entra) mode operational. 🔓
|
|
Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.
|
|
Copilot Engine Smoke Test
Overall: PASS cc @lpcox
|
Smoke Test: Services Connectivity
Overall: PASS
|
|
Enforce Squid proxy egress on Docker sbx daemon in smoke workflows Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "msfeed25.pkgs.visualstudio.com"See Network Configuration for more information.
|
|
Copilot Network Isolation Smoke Test EGRESS_RESULT allow=pass deny=pass ✅ Allowed domain (github.com) reachable — HTTP 200 Overall status: PASS cc @lpcox Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "example.com"See Network Configuration for more information.
|
Smoke Test: API Proxy OTel Tracing — Results
Overall: 4/5 scenarios pass directly; Scenario 5 shows expected absence of spans (no proxied API traffic to trace) rather than a regression.
|
|
@lpcox
|
|
feat: run dynamic agent enclaves with repository-scoped GitHub MCP identities MCP connectivity: ❌
|
Chroot Version Comparison Results
Overall: FAILED — Node.js version mismatch between host and chroot environment.
|
🏗️ Build Test Suite Results
Overall: 8/8 ecosystems passed — PASS All 18 test projects across 8 ecosystems (Bun, C++, Deno, .NET, Go, Java, Node.js, Rust) built/installed and passed their tests successfully. No clone failures. Java required
|
…#59290) * fix(enclaves): encode dynamic enclave max_identity_ttl in nanoseconds buildMCPGatewayDelegationEnvelope emitted "max_identity_ttl": enclave.Timeout, where enclave.Timeout is an integer number of seconds. mcpg v0.4.17 decodes max_identity_ttl into a Go time.Duration, whose JSON representation is an integer number of nanoseconds. A configured 120-second enclave therefore installed a 120-nanosecond identity ceiling, so mcpg's Store.validateAgainstEnvelope rejected every realistic AWF create-or-confirm request with 403 delegation_request_denied. AWF already sends nanoseconds (secondsToGoDurationNanos in src/enclave/delegation-control-client.ts), so gh-aw was the mismatched side. Encode the value as time.Duration(enclave.Timeout) * time.Second so encoding/json emits the exact nanosecond integer mcpg expects. Before: "max_identity_ttl":120. After: "max_identity_ttl":120000000000. The runtime envelope expiry clamp (expires_at, MCP_GATEWAY_DELEGATION_EXPIRES_AT, buildDynamicEnclaveExpiryScript) is a separate contract and remains in seconds / RFC3339, unchanged. Also bound enclaves[].timeout for dynamic enclaves at maxDynamicEnclaveTimeoutSeconds = 4740, matching gh-aw-firewall's MAX_ENCLAVE_TIMEOUT_SECONDS preflight and the awf-config schema. Previously gh-aw only enforced Timeout > 0, so gh-aw and AWF could disagree about what compiled, and time.Duration(enclave.Timeout) * time.Second could theoretically overflow int64 for pathological values. The new bound also keeps the multiplication trivially inside int64. Tests added: - TestBuildMCPGatewayDelegationEnvelopeMaxIdentityTTLNanoseconds pins the units contract by round-tripping the emitted JSON through a struct mirroring mcpg's Envelope.MaxIdentityTTL time.Duration, and by asserting that a 120-second AWF-requested TTL is accepted while a 121-second one is not - exactly the comparison mcpg's Store.validateAgainstEnvelope performs. - TestValidateDynamicEnclaveBoundsRejectsOversizedTimeout locks the new upper bound. - TestDynamicEnclaveGatewayContract now asserts the exact "max_identity_ttl":120000000000 wire value and explicitly rejects the pre-fix 120 to prevent a units regression. No .lock.yml files reference max_identity_ttl, so no workflows need recompilation. Fixes #59258 Unblocks github/gh-aw-firewall#8195 and the merged github/gh-aw-firewall#8276. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * style: gofmt the enclave timeout constant block The new maxDynamicEnclaveTimeoutSeconds comment split the const block's alignment group, so gofmt rewrapped the two following assignments. CI's lint-go-format caught it. Whitespace only; no behaviour change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: lpcox <lpcox@github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Important
Superseded on one point after merge. This PR described
requested_ttlandmax_identity_ttlas Gotime.Durationnanoseconds. The ecosystemresolved the units mismatch in the opposite direction: mcpg v0.4.18 added
internal/delegation/wire.go, which decodes both fields as whole seconds.AWF was realigned in #8292, gh-aw in
github/gh-aw#59292. The sections beloware preserved as the historical record; see
Post-merge units correction for the contract
that actually shipped.
Implements the remaining AWF runtime path for dynamic, GitHub-MCP-backed agent enclaves under ADR 0001.
A dynamic entry now selects one canonical repository at runtime, receives one short-lived
github-repository-read-v1identity minted through mcpg's private delegation control channel, and reads that repository through GitHub MCP without cloning or mounting a seed. Dynamic script execution remains out of scope.Refs #8195. Deliberately does not auto-close it — see Blocked upstream.
Architecture
Who holds what
The enclave subsystem is a chain of components with deliberately unequal privilege, and the topology is what keeps them apart.
awmg-mcpg, imageghcr.io/github/gh-aw-mcpg)enclave-mcp-server)--cap-drop ALL, read-only, seccomp, one internal networkThe broker is the sharp edge: a
docker.sock:rwmount is root-equivalent on the runner, so anything that reaches and authenticates to it can launch arbitrary containers. That is why it joins exactly one internal network, why AWF asserts that network's membership is precisely{broker, mcpg}and aborts on a third member, and why the network isinternal: true— it holds repository content and must not be able to ship it anywhere. The same threat modelled from the other end is whyenclavesrefuses--enable-dindor a Docker-socket volume mount: exactly one component may hold the socket, and nothing the agent controls may reach it.Note the direction of the one permitted adjacency: mcpg is admitted as a peer on the broker's network. The broker never joins a network where an untrusted workload lives.
Network homing
Several components are multi-homed, so this is stated explicitly rather than implied by a diagram:
awf-netawf-net,awf-extawf-net(+awf-ext)awf-extonly when the agent is not in compose (microVM)awf-netawf-net,awf-enclave-mcp-control,awf-enclave-agent, host loopbackawf-enclave-mcp-controlawf-enclave-mcpawf-enclave-agentawf-enclave-agent,awf-enclave-agent-egress--network none127.0.0.1onlydocs/enclaves-architecture.mdcarries this as a mermaid diagram.Why the control client runs in the AWF host process
gh-aw starts mcpg with
docker run -p 127.0.0.1:<port>:<port>, so the published control port is reachable only from the runner's own loopback interface; gh-aw's own source says as much ("Only the AWF host process receives this variable"). No container can reach a127.0.0.1-published port through the host.That is workable because
awfis not a fire-and-forget launcher. It blocks ondocker waitfor the whole run (container-lifecycle.ts:446), propagates the agent's exit code, and states the invariant outright — "ensure the agent cannot outlive the awf process." It already streams logs and enforces the agent timeout concurrently, so the control client and admission loop live inside a window that is already supervised.The broker still has to route every
enclave_run_agentthrough canonical admission before repository content is exposed, so it asks the host over an AWF-private request/response directory inside the0700enclave private root, bind-mounted only into the broker. No new network listener is created, so nothing new becomes reachable fromawf-net, the enclave agent network, the general MCP route, or the host's external interfaces. If AWF dies mid-run the channel stops answering, admission times out, and the invocation fails closed.The channel carries the caller's selector, the exact finite output-schema hash, one repository, one executor bearer, and one settlement. It never carries the control endpoint, the control capability, the identity handle, the compiler envelope, mcpg's state path, or its policy generation.
What changed
Startup and private handoff
127.0.0.1,[::1]), the exact mcpg control path, no credentials/query/fragment.localhostis rejected as resolver-dependent — a poisoned/etc/hostsor NSS module must not be able to point an AWF-only capability at another listener. An omitted or explicit:80both resolve to 80, since the WHATWG URL parser normalizes them identically.0700private root with exclusive0600files. Neither value is ever mounted into the broker, the executor, the model sidecar, the general MCP route, or the delegated data plane; both are in the primary agent's exclusion set.GH_TOKEN/GITHUB_TOKEN, clone nothing, write no seed catalog (not even an empty one), and mount neither/awf/seednor a seed map. Static and separate static+dynamic behaviour is unchanged.mcpg v0.4.17 control client
Strict clients for
create-or-confirm,status,reconcile,revoke, andrevoke-by-labels. Operation paths are siblings of the controller name in the exported endpoint, not children of it.requested_ttlis a duration on the wire; timestamps are RFC 3339. (As merged this was nanoseconds; the shipped contract is whole seconds — see the note at the top.) Bodies are bounded, timeouts explicit, and every response is validated against the request before it is trusted: non-empty handle and bearer, exact repository match,github-repository-read-v1, exactlylist_issues+issue_read, matching SHA when one was requested, and an expiry no later than the requested TTL or the invocation deadline.Broker integration and lifecycle
DynamicRepositoryRegistryper dynamic entry;enclave_run_agentgoes through canonical admission — form, envelope,maxRepositories, expiry, run-wide quota reservation, shared disclosure ledger — before any workspace, container, or bearer exists.status, revokes stale labelled identities, thenreconcile. Admissions stay blocked until that sequence succeeds.revoke-by-labelssweeps at teardown. An unresolved revocation re-blocks admissions and downgrades the invocation to the canonical error rather than returning a success-shaped result.Dynamic executor
list_issuesandissue_readfor the one admitted repository. No job token, control capability, handle, envelope, state path, generation, or control endpoint — so it cannot authenticate to the control plane (see the correction below for why that is the accurate claim rather than unreachability)./awf/seed: no checkout exists, and cloning, arbitrary URLs, the GitHub CLI, writes, unscoped search, organization/global discovery, and sibling-repository access are all prohibited and fail closed.github-repository-read-v1grants only the two read tools afterwards. Reads are therefore audited aslive, and markedpinnedonly when the control binding actually carries a resolved SHA.Review findings fixed in this PR
A code-review pass found three defects that each made the feature non-functional and were invisible to the suite because the tests entered the pipeline below the broken seam. All three are fixed in
dac7fb87with regression coverage at the layer that was bypassed:runMainWorkflowvalidated the enclave configuration beforeprepareEnclavestook custody of the handoff, so every dynamic run aborted at startup. Taking custody deletes both environment variables, so exactly one caller may read them; the early gate now validates structure only and defers the handoff check to that single custodian, which still runs before any container exists.runEnclaveContainernever read, so every executor launched withAWF_ENCLAVE_AGENT_DYNAMIC_REPO=undefined. The binding now flows through the spec builder, which revalidates the admitted repository and read mode; only those two scalars may vary per invocation, and reconciliation paths skip the launch-only checks.A follow-up pass confirmed the fixes and found nothing new.
Correction to an inherited reachability claim
Worth flagging explicitly for review. An earlier revision of this branch stated that neither the primary agent, the broker, the executor, nor the model sidecar "can route to" mcpg's delegation control listener. That was taken from gh-aw's own comment (
enclaves.go:56-57) and repeated without verification. It does not follow, and4783af78corrects the docs and source comments.-p 127.0.0.1:<port>:<port>bounds who can reach the port through the host. Under network isolation gh-aw binds the in-container listener to0.0.0.0— it has to, since Docker NATs a published port to the container's bridge IP and a container-local127.0.0.1bind would be unreachable. A peer sharing a Docker network with mcpg addresses the container IP directly and never traverses the published port. Because mcpg is a single container serving both planes, and the executor sits with it onawf-enclave-agentat172.31.0.40, publication scope does not hold the executor off the control port.Nothing in the implementation changes. The control plane was always protected by the AWF-only 256-bit capability, which is never placed in any container's environment or mount, and which mcpg checks on every request before returning
403 delegation_access_denied. The enforced control is authentication, which is the stronger property; the prose simply described it as the weaker and incorrect one. The primary agent is additionally constrained by the agent container's default-deny egress (containers/agent/setup-iptables.sh:480); the executor is not, so it relies on capability authentication alone.This is the asymmetry worth carrying into review: the broker gets two independent controls (network non-membership plus authentication), because it can be confined to one network. mcpg gets one, because a single container serving two planes must be co-attached with every peer it serves.
Two caveats: the
0.0.0.0bind is conditional onisAWFNetworkIsolationEnabledin gh-aw and is not empirically confirmed here, and the executor runs--cap-drop ALL. This is a gap in the description, not a known break — but gh-aw's comment is worth correcting upstream.Validation
npm run lint— 0 errorsnpm run type-check— cleannpm run build— cleannpx jest— 356 suites / 5801 tests pass (up from 348 / 5600 onmain), no leaked handlesNew coverage: endpoint/capability preflight and custody, the exact mcpg wire contract and response validation, a cross-component fixture pinned to gh-aw and mcpg v0.4.17 so drift fails a test, host-side admission/recovery/settlement/shutdown, the private channel end to end against a real loopback control server, broker topology and mount absence, bearer-only executor configuration, dynamic-only staging, retry and concurrency, quota settlement, revocation on every path, audit redaction, the admission channel's translation under a split ARC/DinD filesystem, and static regression. The vacuous test added by #8233 is replaced with handoff-driven cases, and its dead validator and path constants are removed.
No enclave integration tests exist in
tests/integration(they would need Docker plus a live mcpg and gh-aw); container behaviour is covered the way the rest of this subsystem is, by requiring the container JS directly from Jest.Post-merge units correction
Resolved. At merge time this PR was blocked: gh-aw emitted
max_identity_ttlas an integer of seconds while mcpg v0.4.17 decoded it astime.Durationnanoseconds, so a 120-second enclave installed a 120-nanosecond ceiling and everycreate-or-confirmreturned403 delegation_request_denied.The fix landed as seconds everywhere, not nanoseconds everywhere:
internal/delegation/wire.go.EnvelopeWire.MaxIdentityTTLSecondsandCreateOrConfirmRequestWire.RequestedTTLSecondsareint64seconds, converted bydurationFromWireSeconds.buildMCPGatewayDelegationEnvelopekeeps"max_identity_ttl": enclave.Timeout(seconds).github/gh-aw#59290, which had switched it to nanoseconds, was superseded bygithub/gh-aw#59292(commit7378bee1).requested_ttlto whole seconds.secondsToGoDurationNanosis gone; the contract fixture now pinsrequested_ttl: 120.Verified by decoding AWF's real emitted request bytes through mcpg v0.4.18's actual
wire.go:The superseded nanosecond value now fails closed, confirming the direction is enforced rather than merely conventional:
Shipped versions: AWF v0.28.14, mcpg v0.4.18, gh-aw
DefaultFirewallVersionandAWFDynamicRepositoryEnclaveMinVersionboth v0.28.14,DefaultMCPGatewayVersionv0.4.18.Still open
github/gh-aw#59268— the delegation control listener is reachable from container peers that share mcpg's bridge network, because-p 127.0.0.1:...bounds only host-published access. Capability authentication still holds, so this is a missing defence-in-depth guarantee rather than an authorization bypass, but #8195's network-isolation criterion is not yet satisfied.Remaining follow-ups from the issue's release checklist are also out of this repository's scope: updating gh-aw's
AWFDynamicRepositoryEnclaveMinVersionfrom the provisionalv0.28.14to the actual AWF release, bumpingDefaultFirewallVersion, and completing #8192.