feat(studio): server/security tier — trusted proxy, request-trust gate, managed-mode controller - #968
Closed
jtenniswood wants to merge 1 commit into
Closed
feat(studio): server/security tier — trusted proxy, request-trust gate, managed-mode controller#968jtenniswood wants to merge 1 commit into
jtenniswood wants to merge 1 commit into
Conversation
…e, managed-mode controller core The layer between the browser and the daemon. server-proxy.ts pins Host/Origin/CSRF against MECATL_STUDIO_PUBLIC_ORIGIN, allowlists headers in both directions, injects the bearer and the session workspace server-side (the browser never learns or chooses paths), and synthesizes 409 deployment-owned answers for every control surface in external mode. request-trust.ts carries the CSRF/DNS-rebinding truth table with its test; controller-security.mjs is the shared browser+controller gate (isLoopbackHost, requestIsAllowed, validateGatewayURL, validSkillName). scripts/local-controller.mjs lands its core: spawn mecated from ../bin/mecated on a random loopback port with a generated bearer (--ready-file + --lifetime-pipe-fd FIFO), auto-restart with rollback, the authenticated relay, /status, /restart, and the MCP gateway set + OAuth-injecting proxy. provider-auth.mjs rides with it (startup provider validation and /status fields import it; booleans and key-shape probes only, never key material). The /skills* and /providers*+/model-router route families arrive with the surfaces that call them later in the series. tests/rendered-html.test.mjs is the hermetic proof: a real next build + next start in external mode against a fake in-process daemon — bearer + workspace injection, the full external-mode 409 table, CSRF/DNS-rebinding, offline → honest 503, HTTPS-only gateway egress. CI's build step upgrades to npm run test:server. The SSR assertion targets root branding until the Chats route lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 3/21 — Studio: server/security tier
The Next server tier and the managed-mode controller core — the layer between the browser and the daemon. The browser never holds a daemon address or credential; everything below is server-side.
In this PR:
src/lib/server-proxy.ts— the trusted proxy: Host/Origin/CSRF pinning (MECATL_STUDIO_PUBLIC_ORIGIN), header allowlists both directions, server-side bearer injection, server-side workspace injection into session-create bodies (the browser never learns or chooses paths), external-mode 409 synthesis for every control surface. Lands minus two later, narrowly-scoped touches: the OIDC authorization swap (PR 12) and the no-fs/debug workspace skip (PR 18) — the static-bearer hunk here is byte-for-byte the pre-OIDC shape, so PR 12's diff reproduces the real refactor.src/lib/request-trust.ts(+ its CSRF/DNS-rebinding truth-table test) andsrc/lib/controller-security.mjs(+test:isLoopbackHost/requestIsAllowed/validateGatewayURL/validSkillName) — the shared browser+controller gates.src/app/api/mecatl/[...path]+src/app/api/mecatl-control/[...path]— the only two routes the browser talks to.scripts/local-controller.mjs(core) — spawnsmecatedfrom../bin/mecatedon a random loopback port with a generated bearer (--ready-file+--lifetime-pipe-fdFIFO, both flags already on main), auto-restart with rollback, authenticated relay,/status,/restart, MCP gateway set + OAuth-injecting proxy. The/skills*and/providers*+/model-routerroute families arrive with the surfaces that call them (PRs 9/11).src/lib/provider-auth.mjs(+29-test suite) rides here because the controller's startup path (provider-kind validation,/statusconfiguredProviders) imports it — booleans and snippets only, never key material.scripts/dev-local.mjs,.env.example(both modes; the OIDC vars arrive with OIDC in PR 12).tests/rendered-html.test.mjs): a realnext build+next startin external mode against a fake in-process recording daemon — no Go binary, no network. Proves bearer + workspace injection, the full external-mode 409 table, CSRF/DNS-rebinding, offline → honest 503, gateway HTTPS-only egress. The SSR assertion targets root branding until the Chats route exists (PR 7). CI's build step upgrades tonpm run test:server.Demo at this stack point:
npm run devspawns controller + daemon (managed) or attaches toMECATL_BASE_URL(external); kill the daemon and the proxy answers an honest 503.Part of the stacked landing of #618 — see PR 1 of the series for the structure.
🤖 Generated with Claude Code