Layer 5 establishes one enclaves subsystem, one AWF-owned MCP server, and mcpg-only access through the compiler handoff contract.
Dynamic repository admission described below is a proposed, version-gated
extension; it is not supported by the current AWF configuration or runtime.
Current entries must declare a non-empty static repos list and use immutable
seeds. Operators MUST NOT configure or rely on dynamic-policy fields until an
AWF release implements them and the required compiler and mcpg version gates
are available.
AWF stages immutable repository seeds on the host, starts one AWF-owned enclave-mcp-server, and exposes enabled executors only through gh-aw-mcpg.
- Script executor —
enclave_run_scriptruns a bounded Python script in a no-network, read-only, single-use sandbox. - Agent executor —
enclave_run_agentruns the pinned Copilot engine in a bounded single-use enclave. Its mandatory peer is the dedicated API proxy;agent.tools.github(or the deprecated legacyagent.github.cli: issues-read-v1marker) also permits a direct connection to compiler-owned shared mcpg. - Shared controls — the
reposlists of theenclavesentries form the only trusted repository catalog; script and agent calls debit the same per-run repository ledger and share one admission lane.
The primary agent never receives a broker socket, wrapper binary, direct MCP server URL, capability, repository seed, ledger state, or alternate transport.
Repository admission has two modes, both served by this same MCP backend:
- Static seed-backed mode — the existing
reposlists in enclave entries form the trusted repository catalog. AWF stages immutable seeds for that catalog before primary-agent work begins. Each invocation selects one catalog entry and exposes only that repository seed to the single-use executor. Static GitHub-enabled agent enclaves keep their current job-lifetime mcpg identity, which covers the union of configured repositories; they do not claim the dynamic mode's one-repository GitHub-MCP identity guarantee. - Dynamic GitHub-MCP-backed mode (planned) — the compiler will provide a closed policy
envelope instead of enumerating repository seeds in workflow frontmatter. Each
invocation provides only a canonical
owner/reposelector, bounded agent prompt, and finite response schema. AWF admits at most one repository for that invocation through the compiler-owned GitHub MCP path and records the admitted default-branch SHA.
Static and dynamic modes are compatible in one workflow run but mutually
exclusive within a single enclave entry: an entry declares either static repos
or a dynamic repository policy. Caller-controlled repository names are never
interpreted as policy. They cannot alter sensitivity, tools, credentials,
runtime, model, image, network, filesystem, resource limits, timeouts, or
quotas. The one-repository guarantee applies to dynamic admissions and to static
seed exposure, not to existing static GitHub MCP identities unless static mode
later adopts per-invocation repository-scoped identities. Cross-repository
aggregation occurs only in the primary agent by combining multiple bounded
enclave results.
The AWF-owned MCP server publishes only the enabled enclave tools:
enclave_run_script({
privateRepo: "owner/repo",
schema: <finite disclosure schema>,
script: <bounded UTF-8 Python source>
})
enclave_run_agent({
privateRepo: "owner/repo",
schema: <finite disclosure schema>,
prompt: <bounded UTF-8 task prompt>
})
Both tool schemas are closed (additionalProperties: false). Callers cannot provide images, runtimes, models, profiles, prompts beyond the bounded payload field, repository catalogs, credentials, timeout overrides, or any other trusted control.
A dynamic repository entry MUST advertise only enclave_run_agent.
enclave_run_script remains available to static seed-backed entries only unless
a future ADR defines a secure dynamic script repository-access topology.
tools/list publishes exactly the enabled tools without revealing repositories,
sensitivity, remaining budget, invocation counts, runtime, engine, profile, or
model configuration. Both executors debit the same live per-repository ledger
and share one serialization lane. A concurrent tool call receives the canonical
error immediately instead of entering an unbounded fixed-timing queue.
enclave-mcp-serverjoins only the privateawf-enclave-mcp-controlnetwork.- The compiler launches
gh-aw-mcpg, labels it for the run, and gives AWF the gateway identity plus the private/mcp/awf-enclaveendpoint. - The server is reachable only through that gateway. AWF never publishes the server on a host port and never hands the primary agent a direct route.
- When the agent executor is enabled, each invocation joins only the private
awf-enclave-agentnetwork. Its steady-state peers are the dedicated API proxy and, only when GitHub access is configured, compiler-owned shared mcpg. - AWF attaches the existing mcpg container directly to that network at
172.31.0.40with aliasawf-enclave-github-mcp. No AWF bridge, GitHub CLI, Squid, primary agent, general API proxy, safe-output service, or other peer joins the network.
The base MCP handoff and late backend rediscovery are present in mcpg v0.4.15, which reports MCP Gateway spec 1.16.0. The earlier minimum remains spec 1.15.0 and a post-v0.4.8 mcpg release.
The optional GitHub path additionally requires compiler support for mcpg
multi-agent identities and policies (tracked by github/gh-aw#57787). The
compiler MUST pin or minimum-version-gate the first supporting AWF release.
Older AWF versions reject the closed github/tools.github fields; there is
no permissive fallback.
The compiler-generated upstream uses connectTimeout: 120 and
toolTimeout: 4860, covering the maximum 4800-second disclosure bucket, up to
one second of secret-independent response jitter, and a bounded transport
allowance. Its tool allowlist contains only the enabled
executor tools. The compiler generates a fresh 64-character lowercase
hexadecimal capability, substitutes it into the mcpg authorization header, and
passes it to AWF without exposing it to the primary agent.
There are two separate authorization hops. AWF's upstream contract
authenticates mcpg to the AWF-owned enclave server with the
AWF_ENCLAVE_MCP_CAPABILITY. mcpg independently authenticates the client-facing
gateway endpoint with its gateway agent ID/API key; that is the credential
returned in mcpg's rewritten gateway output.
Engine config adapters consume mcpg's rewritten output, not AWF's upstream
contract. Therefore adapters MUST treat the client-facing Authorization
header as a runtime-only value: they must not resolve it while generating
configuration, and must never persist the resolved gateway credential under
GITHUB_WORKSPACE or any other agent-readable path. AWF's upstream template
does not enforce this downstream requirement.
gh-aw-mcpg may start before the enclave server. While the backend is
unavailable, mcpg returns retryable HTTP 503 backend_unavailable; AWF retries
the complete initialize handshake with bounded 500 ms backoff until
AWF_ENCLAVE_MCP_READINESS_TIMEOUT_MS expires. Each request is capped by the
remaining readiness budget. Other HTTP, authentication, protocol, and tool
contract failures are terminal. Neither component may downgrade or bypass the
gateway, and readiness errors never log response bodies, headers, or
capabilities.
After primary-agent work stops, AWF gives the enclave server a bounded 4860-second stop grace. The server closes admissions, drains its single execution lane, reconciles labelled enclaves, and exits before AWF preserves audit artifacts and disconnects mcpg from the private control network. AWF never stops or removes the externally owned mcpg container.
Credential-isolated GitHub access is disabled by default and configured only on
an agent entry, via the closed tools.github contract:
agent:
model: gpt-5
tools:
github:
allowed:
- list_issues
- issue_read
allowedRepos:
- octo-org/repo-b
minIntegrity: noneallowedis a non-empty subset of the two supported tools,list_issuesandissue_read.allowedReposis a non-empty array of exactowner/repositoryslugs; every entry must also appear in the enclosing enclave entry'sreposlist. AWF rejects any entry that is not.minIntegrity, when supplied, is one ofnone,unapproved,approved, ormerged.
AWF validates this shape and wires the enclave-only shared MCP gateway
connection, but it never broadens or replaces the repository and integrity
policy: that policy is enforced entirely by the compiler-created,
enclave-specific mcpg identity described below. AWF derives its own readiness
check — the exact set of tools the gateway must (and must only) advertise —
from the configured allowed list, rather than a fixed pair.
The original closed profile remains supported during migration:
agent:
model: gpt-5
github:
cli: issues-read-v1This is equivalent to tools.github.allowed: [list_issues, issue_read] with no
AWF-side repository or integrity restriction beyond what the compiler's mcpg
identity already enforces. agent.github and agent.tools.github are mutually
exclusive: AWF rejects a configuration that sets both.
The compiler provides the shared gateway handoff
AWF_ENCLAVE_MCP_GATEWAY_CONTAINER, AWF_ENCLAVE_MCP_GATEWAY_ENDPOINT, and
AWF_ENCLAVE_MCP_GATEWAY_IDENTITY, plus a distinct
AWF_ENCLAVE_GITHUB_MCP_AGENT_ID. It configures that enclave identity in
mcpg's gateway.agentIds and gateway.agentPolicies, allowing only the
github server, the configured tools, and the repositories from the trusted
enclave catalog. The primary agent never receives the enclave identity.
AWF validates and stages the enclave identity in a mode-0600 private file,
removes it from the host environment, and copies it into each invocation's
private workspace. The single-use enclave mounts that copy read-only and sends
it directly as the Authorization value to
http://172.31.0.40:8080/mcp/github. AWF initializes a session through that
endpoint before primary-agent work begins and requires the advertised tool set
to be exactly the configured allowed list — no more, no fewer.
The mcpg identity is job-lifetime, not per-invocation. Consequently, mcpg enforces the union of repositories configured for the enclave agent rather than binding the credential to one invocation's assigned repository, and the identity is not independently expired or revoked after each invocation. This weaker lifetime and repository scope is an explicit tradeoff of direct shared mcpg access; AWF still isolates each identity file and enclave process and retains its own per-invocation seed, admission, ledger, output-schema, and timing controls.
The enclave image contains no gh executable and fails preflight if one is
available. Copilot receives one invocation-private MCP configuration exposing
only the compiler-policy-limited direct mcpg endpoint. mcpg injects its GitHub
credential and enforces the configured tools and repositories. GitHub response
data remains inside the enclave.
Only the existing finite-schema result, shared ledger debit, and timing bucket
can return to the primary agent. Shutdown drains admissions, removes labelled
enclaves, disconnects compiler-owned mcpg from the enclave network, and then
removes private state. AWF never stops or removes the shared gateway container.
Dynamic repository enclaves are an unimplemented, version-gated design for
extending GitHub access from a static seed catalog to runtime admission while
preserving compiler ownership of security-sensitive bounds. Dynamic mode will
be agent-only: it will enable enclave_run_agent and reject
enclave_run_script because script enclaves are no-network and dynamic mode has
no immutable seed to mount. A future dynamic script mode would need a separate
secure repository-access topology before it could be admitted.
A dynamic selector is accepted only when it is already the ASCII byte sequence
matching
^[a-z0-9](?:[a-z0-9-]{0,38})/(?!\.\.?$)(?!.*\.\.)[a-z0-9._-]{1,100}$.
There is no trimming, case folding, Unicode normalization, URL decoding, or
alternate syntax. The compiler, AWF, mcpg, policy lookup, audit hashing, and
idempotency use those exact canonical UTF-8 bytes; an input variant is denied
before admission. This is distinct from the legacy static catalog's
normalization at trusted-config ingestion.
The compiler-to-AWF envelope is closed and includes allowed owners or exact
repository patterns, sensitivity, the agent executor type, a versioned
GitHub tool policy, maximum admitted repositories, per-invocation
CPU/memory/process/filesystem/network/time/prompt/response limits, total
quotas, audit labels, and an absolute expiry no later than the workflow job
lifetime. AWF rejects unknown envelope fields and fails closed if any requested
bound cannot be enforced by AWF, mcpg, the runtime registry, or the executor.
Admission,
identity delegation, live-read setup, executor startup, revocation, and cleanup
failures do not fall back to static mode, the existing job-lifetime GitHub
identity, or a broader policy.
The only initial dynamic GitHub tool policy is github-repository-read-v1, a
closed allowlist of repository-scopable read-only operations. It contains
list_issues and issue_read with arguments confined to the admitted
repository and optional immutable ref or integrity filters. Write operations,
mutation-capable tools, unscoped search, organization/global search, repository
discovery, and any tool whose arguments cannot be mechanically confined to the
admitted repository fail closed until a new versioned policy defines and tests
that confinement.
When implemented, the dynamic invocation flow is:
- The primary agent calls
enclave_run_agentwith one canonical repository selector, bounded prompt, and finite schema. - AWF serializes admission through the shared enclave lane, validates the selector against the compiler envelope, and debits the shared repository ledger before execution.
- AWF asks mcpg's dynamic-delegation controller over the private control
channel to atomically create or confirm an invocation-scoped delegated
identity bound to that run, enclave entry, admitted repository,
github-repository-read-v1tool set, schema, and expiry. - AWF records the admitted repository hash and default-branch SHA, stores the delegated identity only in invocation-private state, and mounts it read-only into the single-use executor.
- The executor may access only the admitted repository through the delegated
GitHub MCP identity and only with
github-repository-read-v1tools. If a GitHub MCP tool supports immutable refs, AWF uses the admitted default-branch SHA; otherwise the audit record marks the data as a live read at that admitted SHA. - On completion, timeout, failure, or shutdown, AWF requests identity revocation, records the revocation state, removes invocation-private state, and admits no other repository until cleanup for the serialized lane is complete.
The compiler is not on the runtime path. At startup it configures mcpg with the
dynamic-delegation controller, the closed github-repository-read-v1 policy,
gateway agent policies, and an AWF-only delegation-control capability. AWF owns
that capability for the workflow run and uses it only over the private
awf-enclave-mcp-control channel to create, confirm, or revoke dynamic
identities inside the compiler envelope. The capability is never exposed to the
primary agent or an enclave executor.
Create/confirm/revoke requests are authenticated and idempotent by (run, enclave entry, invocation id, canonical repository). A create-or-confirm call
must atomically return the same repository binding, tool policy, expiry,
default-branch SHA, and opaque identity handle for an existing key; a mismatch
is terminal and revokes any partial identity. Revocation by identity handle and
by run/enclave labels is idempotent. After mcpg restart, the controller must
reconstruct labelled live delegations before accepting new dynamic admissions;
if reconstruction is incomplete, AWF records the condition, revokes by label
where possible, and fails closed until reconciliation succeeds.
Dynamic mode is version-gated. The compiler may emit dynamic repository policy
only when the selected AWF release implements this contract, gh-aw-mcpg
advertises dynamic delegation API github-repository-delegation-v1 on the
private control channel, and the compiler can install the closed
github-repository-read-v1 tool policy plus the AWF-only control capability.
Older AWF or mcpg components reject dynamic fields with no permissive fallback.
Unlike static GitHub access, the delegated identity is not a reusable job-lifetime credential. mcpg must reject replayed, expired, revoked, wrong-tool, and wrong-repository uses. The primary agent never receives the identity, the policy envelope, raw repository names from denied selectors, GitHub credentials, or a direct enclave transport.
Dynamic admission is idempotent by (run, enclave entry, invocation id, canonical repository): retries receive the same recorded admission and
default-branch SHA or the same canonical denial after expiry or quota
exhaustion. Audit records include mode, enclave entry, invocation id, selector
hash, admitted repository hash, admitted default-branch SHA when available,
policy envelope id and expiry, delegated identity id hash, tool set, quota
debits, timing bucket, executor result, revocation state, and cleanup state.
Private repository names and credentials are not disclosed in primary-agent
outputs.
All inaccessible, nonexistent, out-of-policy, expired, malformed, and over-quota selectors return the same canonical admission-denied error. That error omits the requested owner/repository, policy reason, upstream HTTP status, credential state, and timing detail so dynamic mode does not become an existence oracle. Trusted operators can inspect only redacted audit diagnostics.
- Repository-scope escape: one invocation exposes one admitted repository; every GitHub MCP call is constrained by the delegated identity and approved tool set.
- Search query scope escape: organization-wide or global search is rejected unless represented as repository-scoped reads over separately admitted invocations.
- Confused deputy behavior: AWF never turns a caller-provided name into a broader credential; the compiler-created identity is bound after policy admission.
- SSRF: repository selectors are canonical data, not URLs, hostnames, proxy configuration, or egress allowlist entries.
- Identity replay and stale identities: identities expire no later than the invocation timeout, are revoked on every terminal path, and are rejected after shutdown.
- Races: one admission serialization lane covers static and dynamic calls, preventing concurrent quota bypass or repository/identity mixups.
- Resource exhaustion: compiler-owned quotas bound repository count, invocation count, bytes, CPU, memory, process count, filesystem exposure, prompt size, schema size, runtime, and cleanup grace.
- Existence disclosure: denial reasons collapse to one canonical error and timing bucket; detailed reasons are redacted into audit only.
- Cleanup failures: shutdown closes admissions first, drains or cancels the execution lane within the configured grace period, revokes outstanding identities, reconciles labelled resources, records failures, and fails closed for later admissions until reconciliation succeeds.
- Admission and setup failures: policy lookup, identity delegation, default-branch resolution, runtime-registry lookup, and executor-start errors fail closed without retrying under broader credentials or another repository mode.
See ADR 0001: Agent enclave repository admission for the stable compiler, mcpg, runtime-registry, executor, and integration contract.
No unified gh-aw enclave smoke workflow exists yet, so AWF keeps coverage local and unit-focused instead of inventing unsupported workflow syntax. Current owned-scope guidance points to:
src/services/enclave-mcp-service.test.tssrc/services/enclave-agent-service.test.tssrc/enclave/script-runner-spec.test.tssrc/enclave/agent-runner-spec.test.tssrc/enclave/manager.test.tssrc/enclave/mcp-server.test.tssrc/enclave/agent-mcp-server.test.ts
These tests cover the shared MCP server contract, executor selection, gVisor wiring, fail-closed sbx handling, and the private-network topology assumptions that replaced the legacy smoke and runtime-matrix assets.