From c399e9966343a52b13f89068e2ea7e8464dc7118 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 06:56:01 +0000 Subject: [PATCH] =?UTF-8?q?Repin=20amap-connector-claude=2037875a5=20?= =?UTF-8?q?=E2=86=92=208e201bd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESIGN.md | 2 +- docs/TUTORIAL.md | 2 +- siblings.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index cc54fcd..18bd4e7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -28,7 +28,7 @@ where agents run. | P1 | **A space per agent.** Each agent's lanes (inbox, peer, outbox) are its own | So no agent can read another's mail or write into another's outbox | One sandbox per workspace; `instances//` lanes created at launch | One sandbox per agent **[docs]**. Our tooling creates each agent's lane directories on the host and passes them to that sandbox alone as `bind` mounts in `--driver-config-json` **[docs: `docs/how-it-works/sandboxes/runtimes.mdx:117-131`]**. This works on the Docker and Podman drivers only, and only after the operator enables it on the gateway (§6). OpenShell does not stop two sandboxes from being given the same host path, so exclusivity is our tooling's job, and `verify` checks it (§5) **[code: no such check in `crates/openshell-driver-docker/src/lib.rs:3844-3910`]** | | P2 | **An identity the agent cannot forge** | The runtime binds a sender to its outbox. The address must come from the host, never from the agent | The slug, reported by sandy | The sandbox **name**, read by our tooling on the host and mapped to `@`. The name is a DNS-1123 label of at most 19 characters, unique per workspace, and it survives stop, start and restart **[docs: `workspaces.mdx:178-190`, `sandboxes/overview.mdx:36-48`] [code: `crates/openshell-server/src/grpc/validation.rs:241`, `grpc/mod.rs:140`, `persistence/tests.rs:1027`]**. The agent cannot rename it: the API has no rename RPC, and a sandbox's credential reaches only supervisor RPCs **[code: `proto/openshell.proto`; `architecture/gateway.md:379-385`]**. **Changed in phase 0:** a deleted name can be created again with a new ID, and names are unique only within one workspace. So `membership.json` pins `(workspace, name, id)`, `verify` fails when the ID changes, and one fleet uses one OpenShell workspace. Name reuse is intended: "Names can be reused, so use them only for display and logging" **[docs: `docs/extensibility/supervisor-middleware/operations.mdx:27`]** | | P3 | **Read-only mounts.** Inbound lanes, the connector and the agent's instructions are out of the agent's reach | So a compromised agent can't forge an inbound message or rewrite its own policy | Feature mounts; a write fails with `EROFS` | Two independent layers. (1) **The mount flag.** A `bind` mount with `read_only: true` (the default) becomes a Docker `:ro` bind on the agent's container **[code: `lib.rs:3745-3781`, `lib.rs:5770`]**. The agent can't remount it: the container runs with every capability dropped and `no-new-privileges` **[code: `lib.rs:5784-5797`]**. The host, and the router's own container, keep writing to the source directory, as with sandy. (2) **Landlock.** The filesystem policy lists `inbound/`, `peer/`, the roster and the payload as `read_only` and the outbox as `read_write`. Every other path is inaccessible, and the policy is locked at creation **[docs: `docs/security/best-practices.mdx:32,156-161`]**. The policy applies to mount targets like any other path **[code: `crates/openshell-sandbox/src/sandbox/linux/landlock.rs:224` ff.]**. The host's writes appear inside a running sandbox with no delay: the notice the router wrote is listed inside the running receiver **[verified: L1, 2026-09-30, `docs/POC-REPORT.md`, Unknown 1]**. A write gets `EROFS`, not `EACCES`: the kernel checks the read-only mount before Landlock's hooks run, on every write path (open, truncate, create, unlink, rename) **[verified: L1, 2026-09-30, `docs/POC-REPORT.md`, Unknown 2 and Pass criterion 3]**. Two pitfalls. If the policy's `read_only` and `read_write` lists are both empty, there is no Landlock policy layer at all **[code: `landlock.rs:241-242`]**. And a mount under a `read_write` parent, such as the workdir, gets write access from Landlock, so only the `:ro` flag protects it. Mount targets therefore stay outside every `read_write` path | -| P4 | **A supervised process** that injects deliveries into the session and is restarted if it dies | Otherwise delegations only surface when the agent happens to check its inbox | The manifest's `entry`, run by sandy's relay supervisor | **Changed in phase 0.** OpenShell supervises only one process, the main process: the command after `--` **[docs: `sandboxes/overview.mdx:23`]**. `sandbox exec` is not a supervisor **[docs: `overview.mdx:331`]**. So the main process is **our wrapper** on the read-only payload. It starts `inbox-delivery` in its own restart loop, then runs `claude`. On `main`, `--restart-policy on-failure` replaces the whole runtime if the wrapper exits. Release `v0.1.2` predates that flag, and there the exit is terminal **[docs: `overview.mdx:36-48`; `v0.1.2:crates/openshell-driver-docker/README.md:83`]**. The daemon injects over Claude Code's own Unix socket. OpenShell's seccomp filter leaves `AF_UNIX` alone, and its Landlock code scopes no sockets **[code: `crates/openshell-sandbox/src/sandbox/linux/seccomp.rs:187-215,855-890`]**. **New piece:** the daemon finds its session through `AMAP_DELIVERY_SESSION_SOURCE`, and sandy's lister reads tmux panes, which don't exist here. This deployment ships its own lister, which emits the single `claude` process's socket, read from a record that a SessionStart hook on the payload writes from `CLAUDE_CODE_MESSAGING_SOCKET`, and its key file, which is the session's own peer key `$HOME/.claude/sessions/..key` that Claude Code writes (the path amap-deploy-sandy's `payload/handoff-sessions` reads at `eeecad0`). The hook never writes `CLAUDE_CODE_MESSAGING_TOKEN`: that token proves a connection comes from the session's own child, which the daemon is not (amap-connector-claude `37875a5`). End-to-end injection is **[verified: L1, 2026-09-30, `docs/POC-REPORT.md`, Pass criterion 2 and Unknown 4]**: one agent tasked the other, the daemon reported `delivered`, and the reply reached the sender. L1 ran with the hook's record as the key file; the peer key replaces it and is checked in L2. A receiver set to refuse yields `held`, never a false `delivered` | +| P4 | **A supervised process** that injects deliveries into the session and is restarted if it dies | Otherwise delegations only surface when the agent happens to check its inbox | The manifest's `entry`, run by sandy's relay supervisor | **Changed in phase 0.** OpenShell supervises only one process, the main process: the command after `--` **[docs: `sandboxes/overview.mdx:23`]**. `sandbox exec` is not a supervisor **[docs: `overview.mdx:331`]**. So the main process is **our wrapper** on the read-only payload. It starts `inbox-delivery` in its own restart loop, then runs `claude`. On `main`, `--restart-policy on-failure` replaces the whole runtime if the wrapper exits. Release `v0.1.2` predates that flag, and there the exit is terminal **[docs: `overview.mdx:36-48`; `v0.1.2:crates/openshell-driver-docker/README.md:83`]**. The daemon injects over Claude Code's own Unix socket. OpenShell's seccomp filter leaves `AF_UNIX` alone, and its Landlock code scopes no sockets **[code: `crates/openshell-sandbox/src/sandbox/linux/seccomp.rs:187-215,855-890`]**. **New piece:** the daemon finds its session through `AMAP_DELIVERY_SESSION_SOURCE`, and sandy's lister reads tmux panes, which don't exist here. This deployment ships its own lister, which emits the single `claude` process's socket, read from a record that a SessionStart hook on the payload writes from `CLAUDE_CODE_MESSAGING_SOCKET`, and its key file, which is the session's own peer key `$HOME/.claude/sessions/..key` that Claude Code writes (the path amap-deploy-sandy's `payload/handoff-sessions` reads at `eeecad0`). The hook never writes `CLAUDE_CODE_MESSAGING_TOKEN`: that token proves a connection comes from the session's own child, which the daemon is not (amap-connector-claude `8e201bd`). End-to-end injection is **[verified: L1, 2026-09-30, `docs/POC-REPORT.md`, Pass criterion 2 and Unknown 4]**: one agent tasked the other, the daemon reported `delivered`, and the reply reached the sender. L1 ran with the hook's record as the key file; the peer key replaces it and is checked in L2. A receiver set to refuse yields `held`, never a false `delivered` | | P5 | **Operator-owned membership.** Joining the fleet is a security decision | An agent must never add itself | Include/exclude rules, evaluated by sandy at each launch; `selected.json` | Our tooling decides, from the fleet policy, which OpenShell sandboxes get lanes. A sandbox without lanes is simply not in the fleet. Optionally, a gateway interceptor bound to `CreateSandbox` and `DeleteSandbox` does it **[docs: `docs/extensibility/gateway-interceptors.mdx:28-42`] [code: `crates/openshell-gateway-interceptors/src/routes.rs:18,21`]**. Its `post_commit` phase is fail-open, so it can miss events **[docs: `gateway-interceptors.mdx:135`]**. Polling `openshell sandbox list` or `WatchSandbox` stays as the backstop **[docs: `proto/openshell.proto:83,631`]** | AMAP's own rule that **a sender is bound to its outbox** does most of the diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index fd89439..574b8f9 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -111,7 +111,7 @@ printed. It refuses a checkout with local changes, naming it. Without | Sibling | Pin | |---|---| | amap-router-local | `e43dbba` | -| amap-connector-claude | `37875a5` | +| amap-connector-claude | `8e201bd` | | amap-deploy-sandy | `eeecad0` | | amap-spec | not pinned | diff --git a/siblings.json b/siblings.json index f577fad..4e5f57e 100644 --- a/siblings.json +++ b/siblings.json @@ -1,7 +1,7 @@ { "siblings": [ {"name": "amap-router-local", "url": "https://github.com/proofpoint/amap-router-local", "commit": "e43dbba7ac1a44f06b4fbc5dc8c2ffb2777363c0"}, - {"name": "amap-connector-claude", "url": "https://github.com/proofpoint/amap-connector-claude", "commit": "37875a5ddd55a266a1545c5a03c8ce3bf3018b9e"}, + {"name": "amap-connector-claude", "url": "https://github.com/proofpoint/amap-connector-claude", "commit": "8e201bd7936535ca5fe5700121a7831e466f7cc3"}, {"name": "amap-deploy-sandy", "url": "https://github.com/proofpoint/amap-deploy-sandy", "commit": "eeecad0b5674c5771d0ed366f01b5e31144d2e89"}, {"name": "amap-spec", "url": "https://github.com/proofpoint/amap-spec", "commit": null} ]