You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(auth): magic-link pairing via one-time code in the URL
Let a host print a pairing link (`<origin>/?devframe_auth=<code>`) so opening
it pairs the browser automatically. `connectDevframe` reads the code, exchanges
it for a token, and strips the parameter from the URL before anything else;
the resulting bearer token is persisted, never written back to the URL.
Only the short-lived, single-use code rides the URL — never the bearer. The
behaviour is configurable via the `autoPairParam` client option (defaults to
`devframe_auth`, set `false` to disable).
Add the shared `DEVFRAME_AUTH_URL_PARAM` constant and a node `buildAuthPairingUrl`
helper for hosts to construct the link from the current code (devframe stays
headless, so the host prints its own banner). Document the flow and its
trusted-channel caveat in the security guide and skill.
Copy file name to clipboardExpand all lines: docs/guide/client.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,6 +89,8 @@ const ok = await rpc.requestTrustWithCode('047204')
89
89
90
90
The code is single-use, expires after five minutes, and is rotated after repeated wrong attempts, so re-display the current code if an exchange fails.
91
91
92
+
To pair without typing, a host can print a link embedding the code (`buildAuthPairingUrl(origin)`); `connectDevframe` reads the `devframe_auth` query parameter, exchanges it, and strips it from the URL. Disable or rename it with the `autoPairParam` option.
93
+
92
94
### Re-using an existing token
93
95
94
96
Authenticate with a token obtained elsewhere (e.g. another surface) without reloading:
Copy file name to clipboardExpand all lines: docs/guide/security.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,16 @@ The 6-digit code is single-use, expires after five minutes, is compared in const
31
31
32
32
The bearer token is a secret. It travels to the server on the WebSocket URL (`?devframe_auth_token=…`), so serve over `wss://`/`https://` whenever the surface is reachable beyond loopback. Revoke a token with `revokeAuthToken(context, storage, token)`; affected clients drop to untrusted via the `devframe:auth:revoked` event.
33
33
34
+
### Magic-link pairing
35
+
36
+
To skip typing, a host can print a link that embeds the code and open the browser straight into a paired session. Build it from the current code with `buildAuthPairingUrl(origin)` (devframe stays headless, so the host prints its own banner):
37
+
38
+
```
39
+
Devtools ready — pair this browser: http://localhost:3000/?devframe_auth=123456
40
+
```
41
+
42
+
`connectDevframe` reads the `devframe_auth` parameter, exchanges it, and removes it from the URL before anything else (configurable via the `autoPairParam` client option). Only the short-lived, single-use **code** ever rides the URL — the resulting bearer token is stored, never written back to it. Because the link grants trust to whoever opens it within the code's lifetime, print it only to a trusted channel (the terminal), exactly as you would the bare code.
43
+
34
44
## Practices for tools built on devframe
35
45
36
46
-**Stay on loopback.** The default bind host is `localhost`. Bind to a routable address only when you intend to, and require authentication when you do.
Copy file name to clipboardExpand all lines: skills/devframe/SKILL.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -485,6 +485,7 @@ RPC handlers run with the full privileges of the host process, so the boundary t
485
485
-**`auth` defaults to `true`** — dev-mode connections must pair before calls are accepted. Devframe ships the node primitives (`exchangeTempAuthCode`, `verifyAuthToken` in `devframe/node/auth`); the host adapter (e.g. Vite DevTools) provides the interactive `devframe:anonymous:auth` + `devframe:auth:exchange` handlers and pairing UI.
486
486
-**`auth: false` trusts every reachable connection.** Use it only for single-user `localhost` tools. Never pair it with a non-loopback bind host, a tunnel, or a shared/CI environment. The default bind host is already `localhost`.
487
487
-**Pairing** exchanges a 6-digit one-time code (shown in the developer's terminal) for a node-issued bearer token via `requestTrustWithCode(code)`. The code is single-use, expires in 5 min, compared in constant time, and rotates after repeated failures — show it only in the terminal, never over the network.
488
+
-**Magic-link (optional):** print `buildAuthPairingUrl(origin)` — `<origin>/?devframe_auth=<code>`. `connectDevframe` reads the code, exchanges it, and strips it from the URL (`autoPairParam` to disable/rename). Only the single-use code rides the URL, never the bearer; treat the printed link like the code itself.
488
489
-**Tokens are secrets.** The bearer token rides the WS URL (`?devframe_auth_token=…`) — serve over `wss://`/`https://` beyond loopback. Never log the token or code, never bake them into build output. Revoke via `revokeAuthToken(...)`; clients drop to untrusted on `devframe:auth:revoked`.
489
490
-**Authorize handlers.** Any trusted client can call any registered function — validate inputs, and mark state-changing functions `type: 'destructive'` so MCP/agent clients prompt first.
490
491
-**Origin-lock remote docks** (`originLock`) so a dock token is honored only from its expected origin.
0 commit comments