Skip to content

feat: run dynamic agent enclaves with repository-scoped GitHub MCP identities - #8276

Merged
lpcox merged 7 commits into
mainfrom
lpcox-dynamic-agent-enclave-runtime
Sep 7, 2026
Merged

feat: run dynamic agent enclaves with repository-scoped GitHub MCP identities#8276
lpcox merged 7 commits into
mainfrom
lpcox-dynamic-agent-enclave-runtime

Conversation

@lpcox

@lpcox lpcox commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Important

Superseded on one point after merge. This PR described requested_ttl and
max_identity_ttl as Go time.Duration nanoseconds. The ecosystem
resolved 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 below
are 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-v1 identity 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.

Component Holds Confined by
primary agent nothing enclave-related default-deny egress; no socket, capability, URL, seed, or ledger state
mcpg (awmg-mcpg, image ghcr.io/github/gh-aw-mcpg) the executor-facing data plane and the AWF-only control plane capability authentication; compiler-launched, AWF only attaches
broker (enclave-mcp-server) the Docker socket, rw + work/audit/seed mounts one internal network, membership asserted, no egress
executor one short-lived repository bearer single-use, --cap-drop ALL, read-only, seccomp, one internal network
AWF host process the delegation control capability + admission registry never mounted into any container

The broker is the sharp edge: a docker.sock:rw mount 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 is internal: true — it holds repository content and must not be able to ship it anywhere. The same threat modelled from the other end is why enclaves refuses --enable-dind or 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:

Component Networks Address / note
agent (primary) awf-net 172.30.0.20
squid awf-net, awf-ext 172.30.0.10 — dual-homed, sole egress
api-proxy awf-net (+ awf-ext) 172.30.0.30; awf-ext only when the agent is not in compose (microVM)
doh-proxy / cli-proxy awf-net 172.30.0.40 / 172.30.0.50
mcpg awf-net, awf-enclave-mcp-control, awf-enclave-agent, host loopback 172.31.0.40; four planes
broker awf-enclave-mcp-control one network only; alias awf-enclave-mcp
executor awf-enclave-agent single-use
enclave-agent-api-proxy awf-enclave-agent, awf-enclave-agent-egress 172.31.0.30 — direct egress, never via Squid
script enclave none --network none
AWF host process host — no Docker network reaches 127.0.0.1 only

docs/enclaves-architecture.md carries 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 a 127.0.0.1-published port through the host.

That is workable because awf is not a fire-and-forget launcher. It blocks on docker wait for 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_agent through canonical admission before repository content is exposed, so it asks the host over an AWF-private request/response directory inside the 0700 enclave private root, bind-mounted only into the broker. No new network listener is created, so nothing new becomes reachable from awf-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.

primary agent ──mcpg /mcp/awf-enclave──▶ enclave MCP broker (container)
                                              │
                          admission request   │ 0700 bind mount, no network
                          settlement report   ▼
                                        AWF host process
                                              │ Authorization: <control capability>
                                              ▼
                    http://127.0.0.1:<port>/internal/awf-enclave-mcp-control/*
                                              │
                                              ▼
                                   mcpg delegation controller
                                              │ executor bearer
                                              ▼
executor (awf-enclave-agent network) ──▶ mcpg /mcp/github ──▶ admitted repository

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

  • Strict endpoint validation: literal loopback hosts only (127.0.0.1, [::1]), the exact mcpg control path, no credentials/query/fragment. localhost is rejected as resolver-dependent — a poisoned /etc/hosts or NSS module must not be able to point an AWF-only capability at another listener. An omitted or explicit :80 both resolve to 80, since the WHATWG URL parser normalizes them identically.
  • Custody is taken before any inherited environment is assembled, on every run including static-only ones, and staged into the 0700 private root with exclusive 0600 files. 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.
  • A missing, partial, or malformed handoff is terminal. There is no fallback to a static seed catalog, a job-lifetime identity, or a broader policy.
  • Dynamic-only runs need no GH_TOKEN/GITHUB_TOKEN, clone nothing, write no seed catalog (not even an empty one), and mount neither /awf/seed nor 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, and revoke-by-labels. Operation paths are siblings of the controller name in the exported endpoint, not children of it. requested_ttl is 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, exactly list_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

  • One DynamicRepositoryRegistry per dynamic entry; enclave_run_agent goes through canonical admission — form, envelope, maxRepositories, expiry, run-wide quota reservation, shared disclosure ledger — before any workspace, container, or bearer exists.
  • Recovery calls status, revokes stale labelled identities, then reconcile. Admissions stay blocked until that sequence succeeds.
  • Every terminal path settles the reserved output-byte and execution-second quotas and revokes the identity: success, agent failure, schema failure, timeout, cancellation, broker error, shutdown, and an unexpected throw. revoke-by-labels sweeps at teardown. An unresolved revocation re-blocks admissions and downgrades the invocation to the canonical error rather than returning a success-shaped result.
  • Invocation charges stay committed after a failure, per the ADR: the envelope revealed the opportunity to spend them the moment it admitted the repository.

Dynamic executor

  • Invocation-private, bearer-only GitHub MCP configuration confined to list_issues and issue_read for 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).
  • Instructions rewritten away from /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.
  • The optional admitted SHA is omitted rather than obtained by widening a token or tool: AWF has no already-authorized, repository-confined path to resolve one before the identity exists, and github-repository-read-v1 grants only the two read tools afterwards. Reads are therefore audited as live, and marked pinned only 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 dac7fb87 with regression coverage at the layer that was bypassed:

  1. runMainWorkflow validated the enclave configuration before prepareEnclaves took 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.
  2. The per-invocation binding was passed to the runner in a config object runEnclaveContainer never read, so every executor launched with AWF_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.
  3. The broker's per-launch network-isolation proof admitted the shared gateway only for the static profile, so it rejected the very topology a dynamic run creates. It now admits the gateway for either shape, with the real container name.

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, and 4783af78 corrects 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 to 0.0.0.0 — it has to, since 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 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 on awf-enclave-agent at 172.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.0 bind is conditional on isAWFNetworkIsolationEnabled in 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 errors
  • npm run type-check — clean
  • npm run build — clean
  • npx jest356 suites / 5801 tests pass (up from 348 / 5600 on main), no leaked handles

New 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_ttl as an integer of seconds while mcpg v0.4.17 decoded it as time.Duration nanoseconds, so a 120-second enclave installed a 120-nanosecond ceiling and every create-or-confirm returned 403 delegation_request_denied.

The fix landed as seconds everywhere, not nanoseconds everywhere:

Component Resolution
mcpg v0.4.18 Added internal/delegation/wire.go. EnvelopeWire.MaxIdentityTTLSeconds and CreateOrConfirmRequestWire.RequestedTTLSeconds are int64 seconds, converted by durationFromWireSeconds.
gh-aw buildMCPGatewayDelegationEnvelope keeps "max_identity_ttl": enclave.Timeout (seconds). github/gh-aw#59290, which had switched it to nanoseconds, was superseded by github/gh-aw#59292 (commit 7378bee1).
AWF #8292 changed requested_ttl to whole seconds. secondsToGoDurationNanos is gone; the contract fixture now pins requested_ttl: 120.

Verified by decoding AWF's real emitted request bytes through mcpg v0.4.18's actual wire.go:

AWF requested_ttl on wire = 120 -> decoded 2m0s
gh-aw max_identity_ttl on wire = 120 -> decoded 2m0s
PASS: AWF 120s request accepted by gh-aw 120s envelope under mcpg v0.4.18

The superseded nanosecond value now fails closed, confirming the direction is enforced rather than merely conventional:

FAIL ToRequest: requested_ttl exceeds the maximum supported duration

Shipped versions: AWF v0.28.14, mcpg v0.4.18, gh-aw DefaultFirewallVersion and AWFDynamicRepositoryEnclaveMinVersion both v0.28.14, DefaultMCPGatewayVersion v0.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 AWFDynamicRepositoryEnclaveMinVersion from the provisional v0.28.14 to the actual AWF release, bumping DefaultFirewallVersion, and completing #8192.

lpcox and others added 3 commits September 7, 2026 08:40
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>
Copilot AI balanced review requested due to automatic review settings September 7, 2026 16:20
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview

Documentation build failed for this PR. View logs.

Built from commit de1b6e3

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 93.93% 94.05% 📈 +0.12%
Statements 92.58% 92.71% 📈 +0.13%
Functions 93.20% 93.33% 📈 +0.13%
Branches 86.34% 86.63% 📈 +0.29%
📁 Per-file Coverage Changes (10 files)
File Lines (Before → After) Statements (Before → After)
src/enclave/paths.ts 100.0% → 97.0% (-2.95%) 100.0% → 97.0% (-2.95%)
src/commands/main-action.ts 97.8% → 96.8% (-0.99%) 97.9% → 96.9% (-0.99%)
src/cli-workflow.ts 98.6% → 98.7% (+0.09%) 98.6% → 98.7% (+0.09%)
src/services/enclave-mcp-service.ts 97.8% → 97.9% (+0.14%) 95.7% → 96.0% (+0.26%)
src/enclave/github-gateway.ts 82.3% → 82.6% (+0.21%) 79.4% → 79.7% (+0.32%)
src/enclave/preflight.ts 90.0% → 90.2% (+0.24%) 90.5% → 90.7% (+0.22%)
src/enclave/manager.ts 87.8% → 88.4% (+0.58%) 86.3% → 87.0% (+0.78%)
src/log-directory-setup.ts 96.2% → 100.0% (+3.78%) 96.3% → 100.0% (+3.71%)
src/types/enclave-options.ts 93.8% → 100.0% (+6.25%) 94.4% → 100.0% (+5.56%)
src/enclave/dynamic-registry.ts 92.6% → 99.3% (+6.69%) 90.9% → 98.0% (+7.10%)
✨ New Files (6 files)
  • src/enclave/delegation-control-client.ts: 94.6% lines
  • src/enclave/dynamic-delegation-channel.ts: 92.2% lines
  • src/enclave/dynamic-delegation-handoff.ts: 97.1% lines
  • src/enclave/dynamic-delegation-protocol.ts: 96.8% lines
  • src/enclave/dynamic-delegation-service.ts: 97.3% lines
  • src/enclave/dynamic-delegation.ts: 92.8% lines

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copilot AI left a comment

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.

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>
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Copilot review passed with no inline comments.

@lpcox Add the ready-for-aw label to this PR to trigger agentic CI smoke tests.

lpcox and others added 2 commits September 7, 2026 10:31
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>

Copilot AI left a comment

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.

Copilot review overview

🔵 Needs a closer look

The security-sensitive cross-process identity, quota, revocation, and container-isolation changes require final human review despite extensive automated coverage.

Review tier: Balanced
Findings: None

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>
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Gemini reports failed. Facets need polishing...

💎 Faceted by Smoke Gemini

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Claude failed

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.anthropic.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.anthropic.com"

See Network Configuration for more information.

Generated by Smoke Claude for #8276

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📰 DEVELOPING STORY: Smoke Docker Sbx reports failed. Our correspondents are investigating the incident...

📰 BREAKING: Report filed by Smoke Docker Sbx

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Build Test Suite completed successfully!

Generated by Build Test Suite for #8276

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅

📡 OTel tracing validated by Smoke OTel Tracing

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

🛡️ Smoke Copilot Network Isolation confirmed the egress allowlist is enforced. ✅

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • example.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"

See Network Configuration for more information.

🛡️ Egress verdict from Smoke Copilot Network Isolation

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • msfeed25.pkgs.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "msfeed25.pkgs.visualstudio.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Copilot BYOK AOAI (Entra) completed. Copilot AOAI BYOK (Entra) mode operational. 🔓

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Copilot Engine Smoke Test

Overall: PASS

cc @lpcox

📰 BREAKING: Report filed by Smoke Copilot
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: Services Connectivity

  • Redis PING: ✅ (PONG)
  • PostgreSQL pg_isready: ✅ (accepting connections)
  • PostgreSQL SELECT 1: ✅ (1)

Overall: PASS

🔌 Service connectivity validated by Smoke Services
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Enforce Squid proxy egress on Docker sbx daemon in smoke workflows
Repair Squid log ownership in-container before reading logs and teardown
GitHub merged PR review: ✅
GitHub PR details: ✅
Playwright title check: ❌
File write/readback: ✅
Discussion comment: ❌
AWF build: ✅
Overall: FAIL

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • msfeed25.pkgs.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "msfeed25.pkgs.visualstudio.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Copilot Network Isolation Smoke Test

EGRESS_RESULT allow=pass deny=pass

✅ Allowed domain (github.com) reachable — HTTP 200
✅ Blocked domain (example.com) denied — CONNECT tunnel failed (403)

Overall status: PASS

cc @lpcox

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • example.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "example.com"

See Network Configuration for more information.

🛡️ Egress verdict from Smoke Copilot Network Isolation
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Smoke Test: API Proxy OTel Tracing — Results

  • Scenario 1 (Module Loading): otel.js loads successfully; isEnabled() returns true; exports include startRequestSpan, setTokenAttributes, setBudgetAttributes, endSpan, endSpanError, shutdown, isEnabled.
  • Scenario 2 (Test Suite): 3 test suites, 68/68 tests passed (otel.test.js, otel-fanout.test.js, otel-workload-identity.test.js).
  • Scenario 3 (Env Var Forwarding): env-passthrough.ts forwards GITHUB_AW_OTEL_TRACE_ID and GITHUB_AW_OTEL_PARENT_SPAN_ID to the agent; api-proxy-env-config.ts forwards GH_AW_OTLP_ENDPOINTS, OTEL_EXPORTER_OTLP_ENDPOINT, and both trace context vars to api-proxy.
  • Scenario 4 (Token Tracker Integration): token-tracker-http.js contains the onUsage callback hook point for OTEL.
  • ⚠️ Scenario 5 (OTel Diagnostics): No spans exported this run — api-proxy-logs/otel.jsonl was not created because no LLM requests were proxied through api-proxy during this workflow (expected, since this run made no chargeable model calls via the proxy). token-usage.jsonl has 11 records from prior tracking.

Overall: 4/5 scenarios pass directly; Scenario 5 shows expected absence of spans (no proxied API traffic to trace) rather than a regression.

📡 OTel tracing validated by Smoke OTel Tracing
Add label ready-for-aw to run again

@github-actions github-actions Bot added the smoke-copilot-network-isolation Copilot network-isolation egress smoke test label Sep 7, 2026
@lpcox
lpcox deployed to aoai-model September 7, 2026 18:52 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@lpcox
Smoke test results:

  • GitHub MCP Testing: ❌
  • GitHub.com Connectivity: ✅
  • File I/O Test: ✅
  • BYOK Inference Test: ✅
    Running in direct BYOK mode (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw)
    Overall: FAIL

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

feat: run dynamic agent enclaves with repository-scoped GitHub MCP identities

MCP connectivity: ❌
GitHub.com connectivity: ✅
File write/read test: ✅
BYOK inference: ✅
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw) authenticated via Microsoft Entra
Overall status: FAIL

@lpcox

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)
Add label ready-for-aw to run again

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.14 Python 3.12.14 ✅ YES
Node.js v24.20.0 v22.23.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: FAILED — Node.js version mismatch between host and chroot environment. smoke-chroot label not added since not all tests passed.

Tested by Smoke Chroot
Add label ready-for-aw to run again

@lpcox
lpcox deployed to aoai-model September 7, 2026 18:53 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color ok ✅ PASS
Go env ok ✅ PASS
Go uuid ok ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx passed ✅ PASS
Node.js execa passed ✅ PASS
Node.js p-limit passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

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 -Dmaven.repo.local pointing to a writable path (/tmp/gh-aw/agent/m2repo) since ~/.m2 was pre-owned by root; this was worked around locally and did not affect proxy connectivity.

Generated by Build Test Suite for #8276 · copilot · auto · 53.2 AIC · ⊞ 12K ·
Add label ready-for-aw to run again

@lpcox
lpcox merged commit bff2b29 into main Sep 7, 2026
174 of 180 checks passed
@lpcox
lpcox deleted the lpcox-dynamic-agent-enclave-runtime branch September 7, 2026 19:08
lpcox added a commit to github/gh-aw that referenced this pull request Sep 7, 2026
…#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants