Skip to content

fix(devframe): validate authentication link origins - #325

Open
antfubot wants to merge 3 commits into
mainfrom
fix/auth-link-origin
Open

fix(devframe): validate authentication link origins#325
antfubot wants to merge 3 commits into
mainfrom
fix/auth-link-origin

Conversation

@antfubot

@antfubot antfubot commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Why

For handler- and middleware-owned hosts without an explicit public origin, the first inbound request permanently determined the origin used in the terminal's OTP magic link — Node middleware built it straight from the Host header, and the fetch handler trusted the absolute request URL. An unauthenticated first request could therefore redirect the credential-bearing link to an attacker-controlled origin.

Implements plans/006-validate-auth-link-origin.md.

What

  • The advertised origin is now derived from a request only through a strict trust rule: the candidate's parsed hostname must be loopback (isLoopbackHostname, reused from the WS transport), or its canonical origin must exactly match an allowedOrigins entry. Everything else — a raw inbound authority, a dynamic WsOriginRegistry, a disabled gate, forwarded headers — is ignored, and those deployments supply an explicit origin.
  • Candidates are canonicalized and rejected on credentials, paths, query strings, fragments, malformed ports, and non-HTTP(S) schemes.
  • First-valid-origin behavior is preserved: a rejected candidate never prints a banner, registers a poisoned origin, or locks out a later valid one.
  • Explicit origin and owned-listener (bound address/port) behavior are unchanged — both bypass request derivation.
  • Regression tests cover hostile-first/valid-second ordering, exact vs. prefix/suffix allow-list matching, explicit-origin precedence, and protocol/port canonicalization.
  • Security and adapter docs updated to describe the loopback/allow-list policy and to point non-loopback deployments at explicit origin.

Verification

  • pnpm --filter devframe typecheck → exit 0
  • devframe test suite (581 tests, incl. the API-snapshot guard) → pass
  • pnpm lint → exit 0

Pre-existing, change-unrelated environment failures remain on the base branch (@devframes/plugin-git build EACCES, devframe-starter typecheck needing built workspace artifacts, and knip's starter/playground module-resolution errors); none touch the files in this change.

This PR was created with the help of an agent.

@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
devframe Ready Ready Preview Sep 1, 2026 8:20am UTC
Comment on lines +616 to +617
const mod = await import('devframe/rpc/transports/ws-server')
loopbackCheck = mod.isLoopbackHostname

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to utils, avoid importing a whole module just for a utility

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f82f8cd. Moved isLoopbackHostname (and isAllowedOrigin) into a new dependency-free devframe/utils/origin. instance-shell now imports validateOriginCandidate statically from there, so it no longer dynamically imports the whole crossws-carrying ws-server transport just for the loopback check.

* non-HTTP(S) scheme, or an untrusted host). Forwarded headers are never
* consulted.
*/
function validateOriginCandidate(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's group all https / origin related utils

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grouped into devframe/utils/origin: it now holds isLoopbackHostname, isAllowedOrigin, and validateOriginCandidate together. ws-server re-exports the two predicates so its public import path stays intact, and sse-server + the MCP fetch gate now import the check from the util directly rather than through the transport.

Address review: move isLoopbackHostname/isAllowedOrigin out of the
crossws-carrying ws-server transport into a dependency-free
devframe/utils/origin, and group the auth-link origin validation
(validateOriginCandidate) alongside them. instance-shell now imports the
validator statically instead of dynamically importing the whole transport
module. ws-server re-exports both predicates to keep its public API path
intact; sse-server and the MCP fetch gate import the check from the util
directly.
…PI snapshots

- Add the devframe/utils/origin alias to alias.ts (the source of truth that
  regenerates tsconfig.base.json paths), so the new subpath resolves to source
  in vitest and typecheck.
- Add its tsnapi API snapshots and refresh the ws-server snapshot, whose
  representation shifts now that isAllowedOrigin/isLoopbackHostname are
  re-exports rather than local definitions (same public names).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants