feat(security): enforce remote-dock originLock, gate hosted bridges by default, reject Origin-less MCP requests, move OTP to URL fragment - #161
Merged
Conversation
…s, move OTP to URL fragment Harden the connect/MCP auth surfaces: - Wire `isRemoteTokenTrusted` into `createInteractiveAuth`'s connect-time gate so remote-UI dock tokens actually authenticate and `originLock` binds a token to its dock origin (previously dead code — minted, never verified). - Require an `Authorization: Bearer <token>` on the route-based MCP endpoint (`createMcpFetchHandler`), the real gate since the origin check only ever constrains browsers. `createDevServer`/`@devframes/next` mint a per-instance token, record it in the instance-registry file (now written mode 0600), and `devframe connect` presents it automatically. - Move the magic-link OTP from the query string (`?devframe_otp=`) to the URL fragment (`#devframe_otp=`), which the browser never sends to the server, so the one-click code stays out of access logs and Referer headers. BREAKING CHANGE: the hosted bridges (`viteDevBridge`, `createDevframeNextHandler`) now gate their side-car RPC/WS server by default instead of running with `auth: false`. Pass `auth: false` explicitly to keep a single-user localhost host ungated. The route-based MCP endpoint now requires a bearer token; obtain it from the instance registry (or `StartedServer.mcpAuthToken`). Created with the help of an agent.
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
… drop bearer token + meta-token Replace the MCP bearer-token requirement with an origin-only gate, and stop delivering any token through the connection meta: - The route-based MCP endpoint no longer requires `Authorization: Bearer`; it instead rejects `Origin`-less requests (a request must carry a loopback or allow-listed `Origin`), so a route-based endpoint isn't reachable by an arbitrary local process while native clients send their loopback origin. `devframe connect` now sends each instance's own origin. - Revert all token plumbing: `createMcpFetchHandler.authToken`, `StartedServer.mcpAuthToken`, the instance-registry `mcp.token` field and its 0600 file mode, `@devframes/next` `mountMcp`/handler token surfaces. - The hub examples no longer inject an auth token into `__connection.json`; they run their loopback side-car with an explicit `auth: false` (a documented single-user-localhost opt-out). The library default flip stays: viteDevBridge and createDevframeNextHandler still gate by default. Created with the help of an agent.
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.
Why
An audit of the
devframe connect/ MCP / hosted-bridge auth surfaces found that the parts protecting the browser threat are solid (OTP handshake, loopback origin gate, DNS-rebinding protection), but several surfaces were weak against local-process threats — and some protection was documented but never actually wired up. This closes four gaps.What
1a — Remote-dock tokens /
originLockare now enforced.isRemoteTokenTrustedwas minted-and-delivered but had zero call sites, so a remote-UI dock's session token authenticated nothing andoriginLockwas inert. It's now checked increateInteractiveAuth's connect-time gate, binding a dock's session token to its recordedOriginwhenoriginLockis on (the default).1b — Hosted bridges gate by default.
viteDevBridgeandcreateDevframeNextHandlerpreviously defaulted their side-car RPC/WS server toauth: false, leaving it reachable by anything that could open the socket. They now defer tocreateDevServer's gate (devframe's interactive OTP unlesscli.authopts out). This is a secure-by-default change with a one-line opt-out (auth: false); the reference hub examples do exactly that (loopback-only, no auth UI), with a comment pointing at the security guide.2 — The route-based MCP endpoint rejects
Origin-less requests. Its origin gate previously mirrored the WS transport, which allows requests with noOriginheader (for native clients) — so any local process could reach every tool. The MCP route now requires anOriginthat is loopback (or allow-listed); native clients likedevframe connectsend their own loopback origin explicitly. No bearer token and nothing secret in the connection meta.3 — The magic-link OTP moved from the query string to the URL fragment.
?devframe_otp=→#devframe_otp=. The fragment is never transmitted to the server, so the one-click auto-auth code stays out of access logs andRefererheaders while keeping the same UX. Only the short-lived, single-use code ever rides the URL; the bearer token does not.Notes
viteDevBridge,createDevframeNextHandler) gate their side-car by default. A single-user localhost host that owns the trust boundary another way opts out withauth: false— a one-line change.Origin-less requests — a native client must send a loopbackOrigin(asdevframe connectdoes). NoteOriginis only a trustworthy signal from real browsers; this closes the naive Origin-less path and keeps DNS-rebinding protection, but is not a hard authenticator against a local process that spoofs a loopbackOrigin.?devframe_auth_token=) deliberately stays on the WS URL — it's read server-side from the upgrade request, so a fragment can't carry it; it remains documented as wss-only off-loopback.docs/guide/security.md,docs/adapters/mcp.md,docs/guide/client.md,skills/devframe/SKILL.md.pnpm lint && knip && test (1039 passing) && typecheck && build.This PR was created with the help of an agent.