carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

Reviewed spec 55c9b06f against main’s implementation at 3d444a1b. The relevant implementation is unchanged in the locally available origin/main.

  1. P1 — A flat tenant can impersonate a nested sibling (§§5.4, 6.1–6.2).

Put an ungranted flat instance A and a granted nested instance B on one box. A opens /run/carlos/instances/<B-iid>/engines.sock. The listener attributes the request to B and signs B’s identity. Withholding A’s own socket does not prevent this.

Nested processes use User=carlos, and their run directories are writable binds (carlos-instance@.service:28). The existing migration code explicitly recognises that flat tenants can write siblings’ directories because they share that UID (preflight.go:181). A 0600 socket provides no separation between them.

Fix: Require and enforce a box-wide isolation prerequisite before enabling socket-derived identity, including preventing later flat launches or layout reversions. Otherwise use a caller-authentication mechanism that flat siblings cannot borrow.

  1. P1 — Hostname-only refusal leaves aliases and path routes public (§§5.1, 5.3–5.4).

Register engine.example.com while an alias or a public hostname’s path map still targets its route. A public request using that other hostname misses the internal set but reaches the engine.

Aliases copy the primary’s complete upstream (edge.go:929); path routing returns the target’s upstream (edge.go:1364). Login POSTs can change the selected upstream again after the proposed first gate (edge.go:1602).

There is also no specified canonical comparison: ServeHTTP strips ports but does not lowercase the host, and wildcard lookup can still match a differently capitalised first label (edge.go:1420, edge.go:1202).

Fix: Check the canonical requested hostname before every forwarding branch, then check the final resolved route after login re-resolution and before cache, upgrade or wake dispatch. Specify how registration through an alias identifies the protected route.

  1. P1 — A new or replacement box starts with public access enabled (§§6.3, 7).

Register an engine, then start a replacement home box with its route but no persisted engine mirror. If the bucket is unavailable—or supplies an older, still-valid snapshot predating registration—§6.3 gives it an empty internal set. Public requests can reach the engine. “Never internal on its watch” does not satisfy the deployment-wide anti-goal.

Existing dispatch serves a local route without requiring control-plane freshness, including waking a hibernating route (edge.go:1499, edge.go:1770). Existing signed-record verification checks signature, shape and expiry, not whether a snapshot is the latest (doorkey.go:198).

Fix: Gate a box’s admission to serving on a verified engine-policy checkpoint with a trusted revision floor. Define fail-closed behaviour for missing or corrupt local state and for replacement boxes that cannot obtain that checkpoint.

  1. P1 — The inherited mirror contract can resurrect revoked grants after restart (§§5.2, 6.3–6.4).

Revision 17 grants A access; revision 18 revokes it. A bucket writer restores the still-unexpired revision 17, then the relevant edges restart. The specified door-key mirror precedent forgets its revision floor on restart, so A’s grant can become valid again beyond the promised freshness bound. Persisting only the internal hostname set does not prevent this.

The precedent explicitly keeps revision history only in memory (doorkeys.go:22); construction resets that history (doorkeys.go:89).

Fix: Persist the accepted revision and policy atomically, and apply the durable floor to both public-refusal state and grant admission. A restart must not admit an older grant table. Define recovery from persistence failures rather than importing the door mirror’s weaker restart guarantee.

  1. P2 — The door exceptions do not survive the specified routing path (§§5.3–5.4).

An engine owner uses its valid door key on another box with Host: <engine hostname>. Today, the sending edge checks ownership and sends an ordinary hop (edge.go:1531). That hop carries no door identity, so the new home-side rule refuses it. The promised preservation of the owner’s existing door access therefore fails remotely.

Separately, a granted caller using an engine name still has a door-caller context after authentication (door.go:125). Existing local door scoping rejects another app’s route (edge.go:1612).

Fix: Define the transition from authenticated door caller to engine caller, including which ownership checks it replaces. Give owner-by-hostname calls explicit signed provenance and a receiving-side ownership check; do not admit ordinary unsigned-caller hops to preserve that exception.

  1. P2 — The shared socket needs a lifecycle separate from control listeners (§5.4).

Following the stated control-socket construction pattern creates two problems: a seed runs before any run listener exists, and overlapping generations can replace or close a shared socket.

Control listeners are currently created only for the run role (prepare.go:111). Binding removes the existing pathname (purgelistenunix.go:28), and generation cleanup closes its listeners (unitproc.go:203).

Fix: Specify an instance-level listener manager, available before seed execution, shared across roles and generations, and retired only with the instance’s identity. Define environment delivery separately for each role.

The pathname itself fits: the instance ID is capped at 40 bytes (provision.go:2918), making the proposed path at most 75 bytes. Generation units already bind the whole parent directory (gen.go:48).

  1. P2 — The five-minute contract needs an engine-specific transport and streaming policy (§5.5).

A conversion that produces its first response headers after 45 seconds currently fails: backhaul uses a 30-second response-header timeout (edge.go:2825). Its transport is shared by destination address (edge.go:2938), so changing it globally also changes ordinary traffic.

The body rule also requires a decision: an unknown-length upload cannot be guaranteed below 64 MiB before forwarding without fully reading it first. Existing backhaul forwards through a reverse proxy (edge.go:2900).

Fix: Specify separate engine transport settings, one deadline spanning upload, wake, conversion and response, and an explicit upgrade policy. Choose bounded pre-forward buffering/spooling or revise the promise to permit streaming with partial upstream delivery before an oversize refusal.

Overall verdict: reject. The design needs fixes to caller isolation, public-route coverage and restart/bootstrap authority before implementation.