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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The system is orchestrated by `src/cli.ts` and managed by `src/docker-manager.ts
- **[docs/enclaves-architecture.md](docs/enclaves-architecture.md)** - Unified enclave architecture, MCP gateway handoff, and coverage notes
- **[docs/cloud-hypervisor-foundation.md](docs/cloud-hypervisor-foundation.md)** - Cloud Hypervisor v53.0 microVM backend (preview): REST API client, secure launcher (network-namespace join + privilege drop + Landlock/seccomp in place of a jailer), manager/backend, GitHub-hosted Ubuntu x86_64 KVM runners only
- **[docs/apple-container-runtime.md](docs/apple-container-runtime.md)** - Apple Container microVM backend (preview): `--container-runtime apple-container`, self-hosted bare-metal Apple Silicon macOS 26+ only, `--network none` guest reached solely through the published-socket capability transport, infrastructure-only Docker Compose with loopback-scoped port publication
- **[docs/apple-container-transport.md](docs/apple-container-transport.md)** - Host/guest capability transport design and threat model for the Apple Container backend
- **[docs/apple-container-transport.md](docs/apple-container-transport.md)** - Host/guest capability transport design and threat model for the Apple Container backend, including the externally owned ordinary MCP gateway upstream (`appleContainer.mcpGatewayUpstreamPort`)

## Development Workflow

Expand Down
38 changes: 37 additions & 1 deletion docs/apple-container-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,41 @@ appleContainer:
| `--apple-container-memory` | `appleContainer.memory` | `8G` | Integer with an optional `K`/`M`/`G`/`T`/`P` suffix. |
| `--apple-container-init-image` | `appleContainer.initImage` | derived from registry/tag | Must be digest-pinned. |
| `--apple-container-cli` | `appleContainer.cliPath` | `container` on `PATH` | Absolute path when the CLI is not on `PATH`. |
| `--apple-container-mcp-gateway-upstream-port` | `appleContainer.mcpGatewayUpstreamPort` | unset | Host loopback port of an externally started ordinary MCP gateway. See [MCP gateway](#mcp-gateway). |

### MCP gateway

gh-aw runs its MCP gateway (`awmg-mcpg`) itself, with a plain `docker run`
outside AWF's Compose project, and publishes it on host loopback with its own
authentication. AWF cannot rewrite a publication it does not own, so gh-aw
passes only the port:

```yaml
appleContainer:
previewEnabled: true
mcpGatewayUpstreamPort: 9100
```

AWF then relays `127.0.0.1:<port>` through an AWF-owned Unix socket into the
guest, where the workload reaches it at `http://127.0.0.1:8080`
(`AWF_APPLE_TRANSPORT_MCP_GATEWAY_URL`). The guest still has no NIC.

- **Only a port is accepted**, an integer in `1..65535`. The upstream host is
fixed to `127.0.0.1` and is not configurable, so this cannot point a guest
capability at another machine. A port reserved for AWF infrastructure (Squid,
any API proxy provider port, the CLI proxy) is rejected regardless of which
of those sidecars this run enables.
- **Valid only on this runtime.** Setting it with any other
`--container-runtime` is an error, not a silently ignored field; every other
runtime reaches the gateway over its own Docker network.
- **AWF publishes nothing for it** and requires no Compose service, so the
gateway's port already being in use is the normal case rather than a
startup conflict.
- **Configuring it is not readiness.** AWF health-probes the upstream before
any relay binds; if the gateway is not listening, the transport rolls back
and the agent never starts.
- **This is ordinary MCP infrastructure, not enclave support.** Enclaves stay
rejected on this runtime (see below).

## Supported and unsupported

Expand All @@ -134,7 +169,8 @@ appleContainer:
| Agent timeout, signals, exit codes | ✅ Exit codes propagate verbatim; a timeout kills the VM and reports `124`. |
| Diagnostics and `--keep-containers` | ✅ See [Diagnostics](#diagnostics-and-preservation). |
| **Google Vertex AI** | ❌ The Vertex provider port is not in the transport allowlist. Rejected at validation rather than silently losing its endpoint. |
| **Enclaves** | ❌ The enclave MCP gateway is a Docker-network peer that has not been proven reachable from a NIC-less guest. |
| Ordinary MCP gateway (gh-aw `awmg-mcpg`) | ✅ Bridged as a capability when `appleContainer.mcpGatewayUpstreamPort` is set. See [MCP gateway](#mcp-gateway). |
| **Enclaves** | ❌ The enclave subsystem is a set of Docker-network peers that have not been proven reachable from a NIC-less guest. Rejected regardless of `mcpGatewayUpstreamPort`. |
| **`--topology-attach`** | ❌ Externally owned peers are not published to macOS loopback, so they cannot be bridged. |
| **Docker-in-Docker / ARC split filesystems** | ❌ The guest never receives a Docker socket. |
| **`--enable-host-access`, `--allow-host-ports`** | ❌ Only allowlisted capability sockets cross the boundary. |
Expand Down
40 changes: 39 additions & 1 deletion docs/apple-container-transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ DoH, IPv6, raw sockets, the `169.254.169.254` metadata address, and every host
network path simply do not exist inside the VM.

A NIC-less guest still has to reach a handful of AWF services that keep running
under Docker Compose. The only transport Apple Container offers such a guest is
under Docker Compose, plus — when configured — one ordinary MCP gateway that a
caller such as gh-aw starts on host loopback outside the AWF Compose project.
The only transport Apple Container offers such a guest is
`--publish-socket host_path:container_path`, which exposes one host Unix socket
at one guest path and works with no NIC attached. Most tooling — curl, npm, pip,
the agent CLIs — speaks TCP to a proxy endpoint and cannot be pointed at a Unix
Expand Down Expand Up @@ -69,6 +71,42 @@ requires.
A capability whose socket was never published fails to dial, and the connection
is closed with no data — the same fail-closed outcome as a disabled capability.

## Externally owned upstreams

Most capabilities front an AWF Compose sidecar, so AWF both publishes the host
port (loopback-scoped) and relays it. One capability does not: the ordinary MCP
gateway. gh-aw starts `awmg-mcpg` itself with a plain `docker run`, outside the
AWF Compose project, and binds it to `127.0.0.1`. AWF has no Compose service to
rewrite and no port to publish, so it is told only the port number, through
`appleContainer.mcpGatewayUpstreamPort` (`--apple-container-mcp-gateway-upstream-port`).

Consequences of that split, all of them deliberate:

- **Capabilities are a superset of publications.** `planAppleContainerInfrastructure`
emits the external capability with no publication and no entry in
`plan.services`, so `applyAppleContainerLoopbackPublishing` never searches the
Compose output for a service AWF does not generate, and the preflight port
conflict probe never reports the gateway's own listener as a collision.
- **Only a port is configurable.** The upstream host is fixed to `127.0.0.1` in
code, so this setting cannot widen the set of addresses a relay will dial. It
is re-validated through the same loopback/private-address predicate every
other upstream passes, and any port reserved for AWF infrastructure is
refused — otherwise the guest's MCP gateway endpoint could silently front
Squid or a credential-injecting API proxy port. The reserved set is fixed, so
the guard does not depend on which sidecars a given run enables.
- **The guest shape is unchanged.** The guest still reaches the gateway at the
compiled-in `http://127.0.0.1:8080` (`AWF_APPLE_TRANSPORT_MCP_GATEWAY_URL`),
whatever the host port is. No contract version bump: the host port is a host
dial target, not part of the host/guest agreement.
- **Configuration is not readiness.** Setting the port proves nothing. The
transport's upstream health probe must connect to the gateway before any relay
binds, and a gateway that is not up rolls the whole transport back and
prevents agent execution.

This is ordinary MCP infrastructure, not enclave support. Enclaves remain
rejected outright by `assertAppleContainerPreSecurityCompatibility`, as does
`--topology-attach`.

## Contract versioning

The host half (`transport-capabilities.ts`) and the guest half (`contract.go`)
Expand Down
13 changes: 13 additions & 0 deletions docs/awf-config-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ supported/unsupported matrix, and
[docs/apple-container-transport.md](./apple-container-transport.md) for the
transport's own threat model.

`appleContainer.mcpGatewayUpstreamPort` bridges one *ordinary* MCP gateway that
a caller starts on host loopback outside AWF's Compose project (gh-aw's
`awmg-mcpg`). AWF MUST accept only a TCP port in `1..65535`; the upstream host
is fixed to `127.0.0.1` and MUST NOT be configurable. AWF MUST NOT publish a
Compose port or require a Compose service for it, MUST reject any port reserved
for AWF infrastructure services, MUST reject the setting on any other
`containerRuntime`, and MUST
health-probe the upstream before the agent starts. The guest reaches the gateway
at the compiled-in `http://127.0.0.1:8080` through
`AWF_APPLE_TRANSPORT_MCP_GATEWAY_URL`. This is ordinary MCP infrastructure and
does not enable enclaves, which remain rejected on this runtime.

Live end-to-end validation runs only on a self-hosted bare-metal Apple Silicon
runner via
[`smoke-apple-container.yml`](../.github/workflows/smoke-apple-container.yml),
Expand Down Expand Up @@ -319,6 +331,7 @@ AWF settings MAY be supplied via config files, including stdin (`--config -`).
- `appleContainer.memory` → `--apple-container-memory`
- `appleContainer.initImage` → `--apple-container-init-image`
- `appleContainer.cliPath` → `--apple-container-cli`
- `appleContainer.mcpGatewayUpstreamPort` → `--apple-container-mcp-gateway-upstream-port` *(host loopback port of an externally started ordinary MCP gateway; host fixed to `127.0.0.1`, valid only with `container.containerRuntime: "apple-container"`)*
- `chroot.binariesSourcePath` → *(config-only; mounts a runner-side binaries directory at `/tmp/awf-runner-bin` inside chroot mode and prepends it to `PATH`)*
- `chroot.identity.home` → *(config-only; forwarded as `AWF_CHROOT_IDENTITY_HOME` and applied after chroot pivot)*
- `chroot.identity.user` → *(config-only; forwarded as `AWF_CHROOT_IDENTITY_USER` and applied to `USER`/`LOGNAME` after chroot pivot)*
Expand Down
6 changes: 6 additions & 0 deletions docs/awf-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,12 @@
"cliPath": {
"type": "string",
"description": "Absolute path to the Apple \"container\" CLI when it is not on PATH."
},
"mcpGatewayUpstreamPort": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Host loopback TCP port of an ordinary MCP gateway started outside AWF (for example gh-aw's awmg-mcpg). AWF relays 127.0.0.1:<port> into the NIC-less guest through the existing mcp-gateway capability, reachable at http://127.0.0.1:8080 inside the guest. The upstream host is fixed to 127.0.0.1 and is not configurable. AWF publishes no port and requires no Compose service for this capability, rejects any port reserved for AWF infrastructure services, and health-probes the upstream before the agent starts. This is ordinary MCP infrastructure, not enclave support; enclaves remain rejected by this runtime. Valid only with container.containerRuntime: \"apple-container\"."
}
}
},
Expand Down
Loading
Loading