carlos / platform Public

Isolated exec spec: revision 11 after codex round 10

Round 10 findings committed unedited. Accepted: InaccessiblePaths=/etc/carlos
in the templates and the probe; the same-uid audit covers credential
inputs a carlos-group process reads, not only directories it writes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZ1ZvWj7naf73GjTX8MBv5
Paul Campbell pushed by paul@keymail.dev c5393728c52bd3ee6ba04b68e26684cf319ca889 parent 251e4d3
2 files changed, +62 −3
  • docs/superpowers/specs/2026-09-11-isolated-exec-design.md +27 −3
  • docs/superpowers/specs/CODEX-FINDINGS-ISOLATED-EXEC-R10.md +35 −0

CI passed — run details

diff --git a/docs/superpowers/specs/2026-09-11-isolated-exec-design.md b/docs/superpowers/specs/2026-09-11-isolated-exec-design.md
index 4c411ca73..c0af6bf55 100644
--- a/docs/superpowers/specs/2026-09-11-isolated-exec-design.md
+++ b/docs/superpowers/specs/2026-09-11-isolated-exec-design.md
@@ -1,7 +1,7 @@
# Isolated exec — every exec-backed instance in its own namespace
-**Date:** 2026-09-11 · **Status:** revision 10, after codex rounds 1 to 9
-(19 + 14 + 7 + 4 + 3 + 1 + 1 + 1 + 2 findings, all accepted; §13) · **Requested by:** discussion
+**Date:** 2026-09-11 · **Status:** revision 11, after codex rounds 1 to 10
+(19 + 14 + 7 + 4 + 3 + 1 + 1 + 1 + 2 + 1 findings, all accepted; §13) · **Requested by:** discussion
#126 (Tito, with box evidence), approved in direction by Paul on
2026-09-11
@@ -614,6 +614,12 @@ TemporaryFileSystem=/run/carlos:ro
# /var/lib as a class. An instance needs nothing from either.
TemporaryFileSystem=/var/cache:ro
TemporaryFileSystem=/var/lib:ro
+# Root-written credentials a carlos-GROUP process may read live here:
+# /etc/carlos/relay-masters is root:carlos 0640 for carlos-relay.service,
+# and an instance's Group=carlos would read it too (codex R10-1). An
+# instance gets its configuration through its launch file and needs
+# nothing from /etc/carlos.
+InaccessiblePaths=/etc/carlos
BindPaths=/data/instances/%I /run/carlos/instances/%I
ReadWritePaths=/data/instances/%I /run/carlos/instances/%I
ProtectHome=yes
@@ -714,6 +720,17 @@ Lines that are load-bearing and why:
drill (`internal/ops/ops.go:274`, `internal/lease/check.go:79`), and
`/var/lib` holds unit tenants' state. Both are hidden. §9 carries the
audit rule that found them, so the next such unit is caught at review.
+- **`InaccessiblePaths=/etc/carlos`** (codex R10-1). `ProtectSystem=strict`
+ makes `/etc` read-only, not unreadable. `carlos-relay.service` runs as
+ `carlos:carlos` and reads `/etc/carlos/relay-masters`
+ (`internal/relay/masters.go:101`), which the relay design stages as
+ `root:carlos 0640` (`2026-09-07-relay-stun-turn-design.md:325`): a
+ file every `Group=carlos` process can read, and the secret every
+ app's relay key is derived from (`internal/relay/server.go:665`).
+ The instance's group is `carlos` for the socket contract (§3.1), so
+ the directory is hidden outright. `host.env` and `apps/*.env` there
+ are root-0600 and were already unreadable; hiding the directory is
+ what makes the next `0640` file safe by default.
- **`/opt/carlos`** stays readable through `ProtectSystem=strict`; the
trampoline and the live symlink are all the instance needs from it.
@@ -1644,6 +1661,9 @@ verdict line:
directories, and `/var/cache` and `/var/lib` list nothing at all;
- a write to `cgroup.kill` under any cgroup directory it can name
(its own included) fails with `EROFS`;
+- `/etc/carlos` cannot be opened (`EACCES` or `ENOENT`, never a
+ listing), and a fixture file the control service stages beforehand
+ as `root:carlos 0640` at `/etc/carlos/probe-secret` cannot be read;
- `/proc` lists only itself, and its own pid is 1;
- a symlink it creates in its directory is refused with `ELOOP`, and
`/proc/self/mountinfo` shows `nosymfollow` on both bound directories;
@@ -1775,7 +1795,8 @@ instance as an enrolled non-`carlos` uid
- `internal/host/systemunits.go:34-83` (active check on the socket)
- `update-platform-binary.sh:52-70` (pinfra): enables the new units by name before restarting the edge
- `harden-host.sh` (pinfra) writes hardening drop-ins for `$TENANT_UNITS`; the instance templates carry their own and must not be listed there
-- **The same-uid audit** (codex R9-1, R9-2): every unit in `internal/host/units/` that runs as `User=carlos` or delegates a `carlos`-owned tree, and every path it writes, is a path an isolated instance must not see. Today: `carlos-edge.service` (`/data`, `/run/carlos`, its delegated cgroup subtree), `carlos-litestream.service` (`/data`), `carlos-hibernate-verify.service` and `carlos-restore-verify.service` (`/var/cache/carlos-verify`). A test over `unitsFS` lists every `User=carlos` unit's `ReadWritePaths=`, `CacheDirectory=`, `StateDirectory=`, `RuntimeDirectory=` and `Delegate=` and asserts each resulting tree is covered by a `TemporaryFileSystem=`, `InaccessiblePaths=` or `ProtectControlGroups=` line in the templates, so a future unit that writes somewhere new fails the build until the templates hide it
+- **The same-uid audit** (codex R9-1, R9-2): every unit in `internal/host/units/` that runs as `User=carlos` or delegates a `carlos`-owned tree, and every path it writes, is a path an isolated instance must not see. Today: `carlos-edge.service` (`/data`, `/run/carlos`, its delegated cgroup subtree), `carlos-litestream.service` (`/data`), `carlos-hibernate-verify.service` and `carlos-restore-verify.service` (`/var/cache/carlos-verify`). A test over `unitsFS` lists every `User=carlos` unit's `ReadWritePaths=`, `CacheDirectory=`, `StateDirectory=`, `RuntimeDirectory=` and `Delegate=` and asserts each resulting tree is covered by a `TemporaryFileSystem=`, `InaccessiblePaths=` or `ProtectControlGroups=` line in the templates, so a future unit that writes somewhere new fails the build until the templates hide it.
+ **Reads count too** (codex R10-1): the same audit covers every file a `User=carlos` or `Group=carlos` unit *reads* that is not world-readable by design, which today is `/etc/carlos/relay-masters` (`root:carlos 0640`, `internal/relay/masters.go:101`) and the alternate path the relay design allows for it; the audit's second table lists each such file, its owner and mode, and the template line that hides it (`InaccessiblePaths=/etc/carlos`). A new credential input for a `carlos`-group reader is added to that table, or the test fails
**Tests that pin the flat layout** (§10): `internal/activator/activator_test.go` (20 fixture sites), `backend_exec_test.go` (4), `admintoken_test.go` (8, path-agnostic), `internal/appconfig/syncexec_test.go` (50), `internal/ops/ops_test.go` (52), `internal/provision/reconcile_test.go` (20), `internal/host/movespass_test.go` (1), `schedule_wiring_test.go` (1), `schedule_token_fence_test.go` (2), root `e2e_test.go` (24), `instances_e2e_test.go`, `config_e2e_test.go`, `deploy_e2e_test.go`. For this release they keep passing on the flat path; each gains a nested twin.
@@ -2067,3 +2088,6 @@ rollback runs the old binary).
findings, every prior item resolved, verdict request changes). Both accepted:
R9-1 `ProtectControlGroups=yes` in the templates and the probe; the probe writes `cgroup.kill` and expects `EROFS` (§4.1, §8.5);
R9-2 `/var/cache` and `/var/lib` hidden; the same-uid audit rule and its test added to §9 (§4.1, §8.5, §9).
+- **Round 10** (codex, `CODEX-FINDINGS-ISOLATED-EXEC-R10.md`, 1 new
+ finding, every prior item resolved, verdict request changes). Accepted:
+ R10-1 `InaccessiblePaths=/etc/carlos` in the templates and the probe; the audit extends to credential inputs a `carlos`-group process reads (§4.1, §8.5, §9).
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-ISOLATED-EXEC-R10.md b/docs/superpowers/specs/CODEX-FINDINGS-ISOLATED-EXEC-R10.md
new file mode 100644
index 000000000..df8a53916
--- /dev/null
+++ b/docs/superpowers/specs/CODEX-FINDINGS-ISOLATED-EXEC-R10.md
@@ -0,0 +1,35 @@
+| Prior finding | Disposition at `251e4d3c` |
+|---|---|
+| R9-1 | **Resolved.** `ProtectControlGroups=yes` covers all tenant roles and the probe; §8.5 requires a refused cgroup-control write. |
+| R9-2 | **Resolved for the reported restore-drill exposure.** `/var/cache` and `/var/lib` are hidden (§§4.1, 8.5). |
+| R8-1 | **Resolved.** Wake refreshes the committed row before `Acquire`; migration reloads routing before releasing admission (§§6.8, 8.2). |
+| R5-2 | **Resolved.** Downgrade checks every row’s paths regardless of flags; unflagging nested rows is refused (§8.2). |
+| R5-3 | **Resolved.** Directory, socket, unit and slice names use the bounded `InstanceID` (§§3.1, 5.3). |
+| R2-4 | **Resolved.** Blocking seed execution and `RemainAfterExit=yes` retain identity and results through capture (§§4.2, 5.3). |
+| R2-5 | **Resolved.** Completion requires terminal state and cgroup emptiness; uncertain stops retain replication and the lease (§§6.3, 6.5). |
+| R2-7 | **Resolved.** Recovery derives both endpoints from the journal’s direction, completes that transaction, then converges to current intent (§8.2). |
+| R2-9 | **Resolved.** Flat minting, explicit reversal and downgrade guards cover the reported failures (§§8.1–8.2). |
+| R2-14 | **Resolved at spec level.** The IPv6 exception is explicit, bounded, rechecked and reported (§8.5). |
+| R1-4 | **Resolved.** Completion is independent of log EOF; blocking observations remain outside the activator mutex (§§6.4–6.7). |
+| R1-5 | **Resolved.** Preparation, execution budget, cancellation, result retention and termination-before-cleanup are specified (§§6.1–6.5). |
+| R1-8 | **Resolved under the recorded decision.** Shared tier limits remain; per-service OOM families are an explicit difference (§4.3). |
+| R1-12 | **Resolved.** Consumer fences and bidirectional recovery cover interrupted migration (§8.2). |
+| R1-13 | **Resolved.** Reverse recovery and completed-reversal downgrade guards address the reported defects (§8.2). |
+| R1-15 | **Resolved for the reported PID/IMDS defects.** No unsafe fallback remains; the behavioural gate is specified (§8.5). |
+| R1-18 | **Resolved under the recorded ruling.** Remote operations and reporting are specified; the `foreign` repair residual remains explicitly accepted (§§8.3–8.4). |
+
+**R10-1 — P1 — §§4.1, 8.5, 9: Isolated tenants can read the platform relay’s region master.**
+
+Retain §5.4’s process-authenticated control service. Extend the filesystem boundary to platform credential inputs.
+
+The [tenant template](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/docs/superpowers/specs/2026-09-11-isolated-exec-design.md:609) leaves `/etc/carlos` visible. `ProtectSystem=strict` makes that directory read-only; it does not prevent reading its files. [systemd execution semantics](https://raw.githubusercontent.com/systemd/systemd/v257/man/systemd.exec.xml).
+
+The existing [relay unit](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/internal/host/units/carlos-relay.service:18) runs as `carlos:carlos`. Its [masters reader](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/internal/relay/masters.go:101) opens `/etc/carlos/relay-masters` directly. The [relay design specifies `root:carlos`, mode `0640`](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/docs/superpowers/specs/2026-09-07-relay-stun-turn-design.md:325), so every isolated tenant’s `Group=carlos` grants the same read.
+
+On a box armed according to that contract, a tenant can therefore read and exfiltrate the region master. This exposes the secret from which the relay [derives each app’s authentication key](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/internal/relay/server.go:665), including other accounts’ keys.
+
+The probe can pass throughout. The new [§9 audit](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/isolated-exec-spec/docs/superpowers/specs/2026-09-11-isolated-exec-design.md:1778) inventories writable directories, so it misses a root-written credential that a platform process only reads.
+
+**Correction:** hide `/etc/carlos` from all three tenant templates and the probe. Extend §9’s audit to credential inputs, including the relay masters file and supported alternate locations. Require a behavioural check that a tenant cannot read a fixture master staged with the relay’s actual ownership and permissions.
+
+**Verdict: request changes.**
\ No newline at end of file