Skip to content

Commit eddc277

Browse files
committed
fix(starter): gate RPC auth by default instead of trusting every connection
`auth: false` was set on every real surface (the CLI's cli.auth, and both Vite playgrounds) - each one auto-trusted any connection that could reach its port, with no interactive handshake at all. That's the framework's documented opt-out for a tool that's genuinely single-user and loopback-only, not a sane starter default: a copy-pasted template should teach the secure-by-default posture, not silently disable it everywhere. Leave `auth` unset on `src/devframe.ts`'s `cli` and both playground configs, so they fall through to devframe's interactive OTP gate (the same default every hosted adapter already ships). A developer who wants to skip the prompt for a one-off loopback session now reaches for the CLI's existing `--no-auth` flag per invocation instead of a baked-in opt-out. The single playground's e2e suite still needs to drive the bridge without solving an interactive code, so `playwright.config.ts`'s `webServer.env` sets an explicit `DEVFRAME_E2E` flag that the playground config checks to disable auth *only* for that automated run - a plain `pnpm run play:single` stays gated. The unit test's own ephemeral, loopback-only `initDevframe` instance keeps `auth: false` outright (it's a private test fixture torn down in `afterAll`, not a listening surface anyone else can reach) - now with a comment explaining why that one is fine. This PR was created with the help of an agent.
1 parent 2e5b0b7 commit eddc277

6 files changed

Lines changed: 39 additions & 8 deletions

File tree

starter/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pnpm run lint
1717
pnpm run typecheck
1818
```
1919

20+
`pnpm run dev` and both playgrounds gate by default: opening the printed URL walks you through devframe's interactive OTP handshake (a 6-digit code) before the SPA can call RPC. That's intentional - see the `auth` comments in `src/devframe.ts` and `playground/*/vite.config.ts` before reaching for `auth: false`, which trusts every connection that can reach the port. For a one-off loopback-only session, pass `--no-auth` to the CLI instead (`pnpm run dev -- --no-auth`).
21+
2022
## File map
2123

2224
| Path | Purpose |

starter/playground/hub/vite.config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ export default defineConfig({
3636
devframes: [entry],
3737
// Use the default hub-ui, rebrand it to match the starter's accent color.
3838
ui: createUi({ branding: { primaryColor: '#10b981', productName: 'Devframe Starter' } }),
39-
// Ungated localhost demo - skip the trust handshake.
40-
auth: false,
39+
// `auth` is left unset: gated by default (devframe's interactive
40+
// OTP - a 6-digit code printed to the terminal, entered once in the
41+
// reference UI's authorization view). `auth: false` would trust
42+
// every connection that can reach this port instead; only reach for
43+
// it on a tool that's genuinely single-user and loopback-only. See
44+
// docs/guide/security.md.
4145
}),
4246
],
4347
})

starter/playground/single/vite.config.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import process from 'node:process'
12
import { fileURLToPath } from 'node:url'
23
import { devframeViteBridge } from '@devframes/vite/single'
34
import { defineConfig } from 'vite'
@@ -29,8 +30,15 @@ export default defineConfig({
2930
plugins: [
3031
devframeViteBridge(devframe, {
3132
base: devframe.basePath,
32-
// Ungated localhost demo - skip the trust handshake.
33-
auth: false,
33+
// Gated by default (devframe's interactive OTP) when unset, same as
34+
// `bin.mjs dev` - see the comment on `cli` in `src/devframe.ts`.
35+
// `DEVFRAME_E2E` is set only by `playwright.config.ts`'s
36+
// `webServer.env`, so the automated e2e suite can drive the bridge
37+
// without solving the OTP prompt; a plain `pnpm run play:single`
38+
// still gates. Don't widen this to a bare `auth: false` - that
39+
// trusts every connection that can reach the port, not just this
40+
// test harness. See docs/guide/security.md.
41+
auth: process.env.DEVFRAME_E2E ? false : undefined,
3442
}),
3543
],
3644
})

starter/playwright.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ export default defineConfig({
2626
],
2727
webServer: {
2828
command: 'pnpm run play:single',
29-
env: { DEVFRAME_E2E_CWD: fixtureCwd },
29+
env: {
30+
DEVFRAME_E2E_CWD: fixtureCwd,
31+
// Tells the bridge to skip the interactive OTP handshake - see the
32+
// `auth` comment in `playground/single/vite.config.ts`. Real usage
33+
// (a human running `pnpm run play:single`) never sets this and stays
34+
// gated.
35+
DEVFRAME_E2E: '1',
36+
},
3037
// The SPA (this playground's own `index.html`) serves from Vite's root -
3138
// see `playground/single/vite.config.ts` for why it can't share the
3239
// RPC bridge's `/__devframe-starter/` base.

starter/src/devframe.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ export default defineDevframe({
2525
command: 'devframe-starter',
2626
port: 7391,
2727
distDir,
28-
// Single-user localhost demo - skip the trust handshake so the served
29-
// SPA can call RPC without an OTP round-trip.
30-
auth: false,
28+
// `auth` is deliberately left unset: gated by default (devframe's
29+
// interactive OTP handshake - a 6-digit code printed to the terminal
30+
// that trusts the browser before it can call any RPC function).
31+
// `auth: false` would trust *any* connection that can reach the port
32+
// instead - see docs/guide/security.md before reaching for it. A
33+
// developer who wants to skip the prompt for a one-off loopback-only
34+
// session can pass `--no-auth` per run (`devframe-starter --no-auth`)
35+
// rather than baking the opt-out into the definition.
36+
//
3137
// Serve the agent (MCP) surface over the dev server's `/__mcp` route.
3238
mcp: true,
3339
},

starter/test/rpc.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ describe('rpc functions', () => {
3737
// directly - an address-family mismatch that reads as a silent hang.
3838
host: '127.0.0.1',
3939
ws: { sidecar: true },
40+
// Fine here (unlike the "real" surfaces in `src/devframe.ts` and
41+
// `playground/`): this instance is a private, ephemeral test fixture
42+
// - bound to loopback, torn down in `afterAll`, and never reachable
43+
// by anything but this test process.
4044
auth: false,
4145
})
4246
await instance.ready

0 commit comments

Comments
 (0)