From f2f3ca12ba8371a115f55cf7c881fe47e597d601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 16:09:03 +0000 Subject: [PATCH] a plugin websocket targets a sidecar, never a port a manifest picked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fixed-port form shipped this morning and lasted about an hour. The owner asked whether declaring a port was safe, and it is not: nothing stopped a manifest naming a port it does not own, so `websocket: { port: 5432 }` plus a grant would have been a raw TCP relay onto Postgres — authenticated by the platform, available to anyone that plugin was granted to. Port 22 and officer's own port are the same sentence. The relay checked that the HOST was loopback and I mistook that for having checked anything about the port. They are different questions and only the second one matters: loopback is exactly where everything worth reaching already is. So the only form is `{ sidecar }`. A sidecar's port is trustworthy for a reason a declaration can never be — the platform watched it arrive at registration, so the association between a port and a plugin is observed rather than asserted. The same distinction that decides the delegation problem in todo/agent-coordination: a claim is not evidence, whoever is making it. The cost is that a plugin wanting a socket needs a sidecar. That is not ceremony, it is the thing that makes the port mean anything. It was added as a shortcut for Minesweeper, whose x11vnc listens on a known port — which is precisely the case that looked harmless and generalised into a hole. Nothing had used it. Minesweeper is still whole in the platform and its extraction had not started. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- src/servers/api/plugin-ws/target.ts | 14 ++++++-------- src/servers/plugins/manifest.ts | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/servers/api/plugin-ws/target.ts b/src/servers/api/plugin-ws/target.ts index 39071d97..bdba0036 100644 --- a/src/servers/api/plugin-ws/target.ts +++ b/src/servers/api/plugin-ws/target.ts @@ -9,13 +9,13 @@ import { snapshotPlugins } from '@@/plugins/mount'; line-oriented sidecar without knowing which is which. The moment this returned a scheme or a path, the relay would have to care what it was carrying. - Two forms, because two things genuinely differ: + The target is always a SIDECAR. A fixed-port form was written and removed the same day: nothing stops a + manifest naming a port it does not own, so `{ port: 5432 }` plus a grant would be a raw TCP relay onto + Postgres for anyone that plugin reaches. Verifying the host is loopback is not verifying the port belongs + to the plugin, and only the second question matters. - { port: 5900 } a fixed port a plugin's own service listens on — `x11vnc` for a desktop, or - anything the plugin starts itself and knows the number of in advance. - { sidecar: 'name' } resolved at connect time, because a sidecar binds an EPHEMERAL port and announces - it. A manifest cannot know that number, which is why the indirection exists rather - than being a convenience. + A sidecar's port is different in kind, not degree: the platform watched it arrive at registration, so the + association is observed rather than asserted. **Host is always loopback and is not configurable.** A plugin socket that could name an arbitrary host would be a request-forgery primitive handed to every plugin author and reachable by anyone the plugin is @@ -32,8 +32,6 @@ export async function pluginWsTargetFor(appName: string): Promise