| 1 | | Previous finding | Round 5 assessment | |
| 2 | |---|---| |
| 3 | | R1-1: flat tenant borrows a nested socket | Fixed by the cgroup check (§5.4). | |
| 4 | | R1-2: aliases and path routes remain public | Fixed for attached aliases and path maps (§5.1). Platform-minted route rows present a separate classification gap below. | |
| 5 | | R1-3: new box starts public | Fixed by classification before serving and refusal of `NULL` (§5.1). | |
| 6 | | R1-4: revoked grants return after restart | Fixed for retained local state; fresh-box replay remains explicitly accepted (§5.3). | |
| 7 | | R1-5: door exceptions fail across routing | Resolved by removing the owner exception and defining the identity transition (§5.4). | |
| 8 | | R1-6: socket lifecycle conflicts | Fixed by instance-level listener ownership (§5.4). | |
| 9 | | R1-7: transport and body limits | Resolved at spec level (§5.5). | |
| 10 | | R2-1: stamp follows serving | Fixed by the admission barrier (§5.1). | |
| 11 | | R2-2: upgrade serves unclassified engines | Fixed by refusal of unclassified routes before listeners open (§5.1). | |
| 12 | | R2-3: flat callers borrow door credentials | Fixed by the door’s UID restriction (§§5.4, 6.2). | |
| 13 | | R2-4: deployment verification fails | Ticket mechanism provided (§5.8); its probe-path source has a separate integration gap below. | |
| 14 | | R2-5: port-80 refusal cannot work remotely | Resolved by the narrowed contract (§5.1). | |
| 15 | | R3-1: previously admitted requests cross the mark | Fixed: acknowledgement waits for cancellation and completion (§5.1). | |
| 16 | | R3-2: flat tenant injects into a nested caller | Fixed at spec level by excluding unisolated execution while armed (§6.2), with historical compromise explicitly outside that guarantee. | |
| 17 | | R3-3: grant-only routes lack watch tickets | Fixed: every deploy target receives a ticket (§5.8). | |
| 18 | | R4-1: foreign instances and workers remain unisolated | Fixed for the reported attack (§6.2). The arming predicate also rejects supported isolated routes; see below. | |
| 19 | | R4-2: path routing widens an engine grant | Fixed by exact-host engine dispatch without path routing (§5.4). | |
| 20 | | R4-3: watch tickets expire mid-watch | Fixed by renewal every four minutes (§5.8). | |
| 21 | |
| 22 | Reviewed spec `50a1f3fc` against locally available amadan `origin/main` at `512baa59`. All implementation files cited below match that revision. No files changed. |
| 23 | |
| 24 | 1. **P2 — Platform-minted hostnames cannot complete classification (§5.1).** |
| 25 | |
| 26 | Start with an ordinary public instance and its automatically minted `<app>.<account>.<apps-domain>` hostname. Upgrade the edge. Both rows receive `NULL`, but the minted row has no live release of its own. Following the specified classifier leaves that hostname refused indefinitely, even with a healthy bucket and a readable source manifest. |
| 27 | |
| 28 | Platform domains are separate registry rows: `mintedRoute` copies the backend into a new `Platform: true` row without a version ([reconcile.go:881](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/domains/reconcile.go:881)). Adoption excludes those rows expressly because they have no independently executed live symlink ([adopt.go:1210](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/release/adopt.go:1210)). The manifest loader returns nothing when that hostname has no live version ([caps_pass.go:111](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/host/caps_pass.go:111)). These rows therefore do not inherit the primary upstream through the separate `route_aliases` fold described in §5.1. |
| 29 | |
| 30 | **Suggested fix:** Define how platform-minted rows resolve to their source route and share its classification and admission barrier. They must remain usable for public sources and become internal before an engine source activates. |
| 31 | |
| 32 | 2. **P2 — The arming predicate rejects isolated unit-backed routes (§6.2).** |
| 33 | |
| 34 | Put a granted nested caller and a DynamicUser-backed engine on the same box. Run `carlos engines arm`. Section 6.2 explicitly permits DynamicUser services, but its requirement that **every `instance` route classify `nested`** rejects the engine. The caller consequently receives no engine socket. |
| 35 | |
| 36 | Unit-backed capability routes are still `Kind == "instance"` ([adopt.go:1225](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/release/adopt.go:1225), [adopt.go:1265](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/release/adopt.go:1265)). Their standard socket is `/run/carlos/apps/<host>.sock` ([carlos-app@.socket:35](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/host/units/carlos-app@.socket:35)). `Classify` recognises only the flat and nested exec paths, so this route classifies `foreign` ([layout.go:103](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/activator/layout.go:103)). Platform-minted instance rows can fail the same test because they copy another hostname’s socket. |
| 37 | |
| 38 | **Suggested fix:** Apply the nested-layout requirement to tenant execution owners using the exec backend. Admit the explicitly permitted isolated unit-backed class separately, and assess generated aliases through their execution owner. Retain the refusal of actual foreign exec launches and workers. |
| 39 | |
| 40 | 3. **P2 — The home edge has no source for the specified deployment probe path (§5.8).** |
| 41 | |
| 42 | Configure an internal app’s build probe as `/healthz`, then deploy it. The CLI polls `/healthz`, but §5.8 permits only `/` or “the release’s declared probe path.” No such declaration exists in the release manifest, and the proposed ticket carries only hostname, version and expiry. The home edge cannot establish that `/healthz` is allowed. |
| 43 | |
| 44 | The console reads the probe from its app database and returns it with the ship response ([apicli.go:2114](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/console/apicli.go:2114), [apicli.go:2285](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/console/apicli.go:2285)). The CLI uses that value ([deploy.go:404](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/deploy.go:404)); `release.Manifest` contains no probe field ([release.go:47](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/design-fleet-internal-engines/internal/release/release.go:47)). |
| 45 | |
| 46 | **Suggested fix:** Include the authorized probe path in the signed watch ticket, sourced from the same configuration the watcher uses. Define the corresponding signed path for bucket-mode watches. |
| 47 | |
| 48 | **Overall verdict: accept with fixes.** The round-4 security findings are addressed at spec level; these three integration gaps remain. |