Repository navigation
Closed
Description
Findings from the adversarial review of the 0.6.3 self-host surface (#143, #147, #148: cloudhal studio --host/--base-url/--behind-proxy/--init-if-empty, serveNode trustProxy). Everything here is in packages/cli and packages/api; the Docker/docs side is #166.
Must fix (security)
- First owner is claimable by whoever reaches a fresh Studio first. The Node path never passes a setup token:
packages/cli/src/commands/studio.ts:140callscreateAuthForwithoutsetupToken,packages/api/src/auth-node.ts:23has none, so a publicdocker run -p 8787:8787 ghcr.io/cloudhal-dev/cloudhalis infirst-usermode for anyone. The Cloudflare path already hasCLOUDHAL_SETUP_TOKEN. Fix: when no owner exists, generate a one-time setup token on start (or takeCLOUDHAL_SETUP_TOKEN), require it on the first sign-up, print the…/login#setup=<token>link to stdout (the self-host guide tells operators to read the logs). Covered by a test inpackages/cli/test/studio-selfhost.test.ts. - Studio reached at any origin other than the configured one cannot sign up or sign in. With
--host 0.0.0.0(the image bakesCLOUDHAL_HOST=0.0.0.0) and no--base-url, Better Auth'sbaseURLstayshttp://localhost:<port>and its trusted origins are only that plushttp://localhost:5173(packages/cli/src/commands/studio-options.ts:105-111). Better Auth 1.7 validates Origin on/sign-up/emailand/sign-in/email(formCsrfMiddleware) and on every cookie-bearing POST, so a browser athttp://<lan-ip>:8787gets 403INVALID_ORIGINon sign-up, sign-in and sign-out, while the API's own CSRF check (packages/api/src/app.ts:160-174, derivesownfrom the Host) passes. Tests cannot see it:NODE_ENV=testmakes Better Auth skip the check. Fix (pick one, document it): pass Better Auth atrustedOriginsfunction that addsnew URL(request.url).originwhen no base URL is configured (matches the API's semantics), or refuse/warn whenserver.hostnameis non-loopback andserver.baseUrlis undefined. Either wayhelp.ts:12-14, the CLI changeset and the printed URL (studioUrl) must stop presenting--base-urlas proxy-only: it is "the origin browsers open Studio at". --no-authis accepted with a non-loopback--hostor--behind-proxy(studio.ts:127,help.ts:9): an unauthenticated all-scopes API published behind a Host-header allowlist any client can forge. Fix: refuse--no-authunless the bind address is loopback and--behind-proxyis off (one clear error).- Rate limits key on client-controlled headers on Node.
packages/api/src/tokens.ts:57-58readsX-Forwarded-For/CF-Connecting-IPregardless oftrustProxy, so when Studio is exposed directly the sign-in and token limits are spoofable (or, with no header, one shared bucket). Fix:serveNodesets the client address from the socket unlesstrustProxy, and then takes the right-most untrusted hop; the API reads only that.
Should fix (same PR or a follow-up)
forwardedUrl(packages/api/src/node.ts:44) trusts the first (client-most) element ofX-Forwarded-Host/X-Forwarded-Protochains; with one trusted proxy the last element is the one it added.--base-urlis not passed asApiDeps.studioOrigin(studio.ts:125), so invite and reset links still derive their origin from request headers.- First-run scaffold is not atomic and signals are handled only after it (
studio-options.ts:148-154): a container stopped during--init-if-emptycrash-loops with "/data is not empty". Scaffold into a temp dir and rename, or writecloudhal.jsonlast. - A bare
--hostfills0.0.0.0(IPv4 only) although help and changeset say "every interface" andcloudhal dev --hostused to passhost: true(packages/cli/src/cli.ts:100); bind::with dual-stack, or say IPv4.
Done when
- Tests for 1–4 in
packages/cli/test/studio-selfhost.test.ts/packages/api/test(Better Auth's origin check is off underNODE_ENV=test, so test the trusted-origins function or the refusal directly). help.ts,packages/cli/CHANGELOG-bound changeset (patch),docs/guides/self-host.md"Beyond localhost" wording agreed with Docker/self-host: backup volume name, atomic site-build, tag-after-gates in docker.yml, guide and compose fixes #166 (that PR owns the guide,.env.exampleand the Dockerfile; this one owns the CLI text).pnpm checkgreen; release note as a 0.6.3 advisory in the changeset ("a fresh self-hosted Studio exposed to a network before its first sign-up could be claimed by anyone who reached it").
Source: adversarial review run after the 0.6.3 release (178 agents, 47 confirmed findings; the self-host cluster is 1–8 above).
Metadata
Metadata
Assignees
Labels
No labels
Activity
fix(cli,api): review follow-ups for the self-host hardening (#165)
Merge pull request #169 from cloudhal-dev/claude/funny-antonelli-4ad09f