Repository navigation
feat: add online handling - #220
Conversation
de822a7 to
4df7213
Compare
4df7213 to
4f1bc83
Compare
4f1bc83 to
13508c1
Compare
85b8094 to
edb24ff
Compare
edb24ff to
ab91c78
Compare
Tchips46
left a comment
There was a problem hiding this comment.
Review: online handling (4fa9f45)
Line numbers are at this PR's head. Checked and fine: the launch token (192-bit randomBytes, timingSafeEqual, stripped by the 303 before the page loads, checked on every route after the host and origin guards including the WS upgrade, HttpOnly + SameSite=Strict cookie), readBody capping while streaming, frame-ancestors 'self' kept, inline scripts allowed by hash, the rpc-session finish ordering, bounded session growth, and the archive skipping symlinks inside the jail.
Must fix
1. An API outage signs everyone out for 60 s (packages/server-core/src/session/session-store.ts:142-161, with api/api-client.ts:210-228 and project/project-registry.ts:188-189)
currentUserre-throws anything that isn't a 401, so the new 15 s timeout (UNAVAILABLE) and any 5xx land in.catch(() => null). That clearssession.projectsand cachesrejectedUntilfor those same cookies.- Example: the API is slow for 20 s while users reload. Each of them is
user: nullfor a full minute after the API is back, every project call returns NOT_FOUND (newsession.user !== nullgate), retrying does nothing because the cookies are the same, and they must reopen each project. - Fix: cache the rejection only when
verifyresolvednull(a real 401). On a thrown error, leave the user, projects and verification as they were.
2. The page policy cuts local games off from anything but loopback (packages/server-core/src/http/shell-csp.ts:16, 27)
- Games are
import()ed into the editor page (packages/runtime/src/service/runtime-service.ts:402), so they run under its policy, andconnect-srconly allowslocalhost/127.0.0.1. - Example: a game that fetches
https://…(a leaderboard, assets from a CDN), connects to awss://or LAN server, or uses[::1]works withnf startand breaks in the editor. The code is already trusted and given'unsafe-eval', so the restriction buys little. - Fix: with
games, useconnect-src *(or addhttps: wss: ws:). Without it, drop thelocallist, since hosted editors run no games. The plugin guide doesn't mention the new policy either.
Should fix
3. Zipping a folder blocks the event loop (packages/server-core/src/fs/archive.ts:53-55)
- The download moved from fflate's worker-based
zip()toZipDeflate.push(wholeFile, true), which deflates synchronously on the main thread. A 500 MiB asset stalls every WebSocket and RPC call for seconds, for every user of a hosted editor. - A file deleted between
listandreadFile(a build writingdist/) throws ENOENT after the 200 has gone out, so the user gets a truncated zip. - Fix:
AsyncZipDeflate(same API, in fflate 0.8.3), and skip entries that throw ENOENT.
4. The token cookie isn't scoped to a port (packages/server-core/src/server/launch-token.ts:5, 37)
- Cookies ignore ports. With two editors on
random(pnpm exampleon 4800 andnf editoron 3000), opening the second link overwrites the first editor's cookie, and the first tab's next RPC call or plugin chunk gets a 401. - The token is also sent to every
localhost:*server the browser opens, so another user of a shared machine (the stated threat) servinglocalhost:8000reads it. - Fix: name the cookie per port (
nf_editor_token_<port>), and document that it still reaches other local ports.
5. nf editor with EDITOR_TOKEN=random doesn't work, but the docs recommend it (docs/docs/getting-started/create-your-first-project.mdx:32, apps/editor/server/main.ts:50-54)
- The token is generated inside the server, and the CLI (
editor.action.ts:22-28) openshttp://localhost:3000/load?path=…without one: that tab gets the 401 page. The server prints only the root link, so the/load?path=target is lost. - Fix: print the token link for the
/load?path=URL, or have the CLI generate the token and pass it through the environment. Until then, drop the docs line.
6. Anonymous callers can make the server buffer 100 MiB per request (packages/server-core/src/server/create-editor-server.ts:126)
readBodyruns before the session is resolved. In a hosted editor, N parallel cookieless 99 MiB POSTs to/rpc/xare all held in memory (chunks, a copy, then a string, about 3× each).- Fix: resolve the session first, and cap at about 1 MiB unless
session.useris set (or the call is a file write).
Nice to have
EDITOR_TOKENlength (packages/server-core/src/env/env.schema.ts,load-env.ts:43-48): a fixedEDITOR_TOKEN=abccan be brute-forced over loopback in milliseconds (no rate limit). Fix: refuse anything under 16 characters other thanrandom.- Stray edits in breakout (
examples/breakout/apps/client/src/scenes/levels.ts:17, 124,components/position.ts:11): an unusedBallimport, brick 6 moved from 793,130 to 795,132, and a@colortag. They look like writes from a manual editor run. Fix: revert both files. - Deleted
CHANGELOG-v2.mdis still cited atCLAUDE.md:302(phase 13 row) anddocs/rewrite-plan.md:1082(13.4). Fix: reword both. shell-csp.ts:3-7says nothing else may receive the page's data, butimg-src https:is an open channel out. Fix: correct the comment, or narrowimg-srcin hosted mode.
Not checked: nothing was run. I couldn't check whether Monaco and the plugins work under the hosted policy, which has no 'unsafe-eval' (e2e only covers local mode).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- session store: tokens are checked on a copy and kept only once the API accepts them; a refusal (401, other 4xx, an answer that is not a user) signs the session out and is cached; an outage keeps the user only for the tokens the session already holds, and the next request asks again - shell CSP: a local editor's page connects over http, https, ws and wss; a hosted one only to its own origin - folder zips: files of 160 kB and more are compressed in a worker, one file at a time, and files deleted since the listing are left out - launch token: the cookie name carries the port; EDITOR_TOKEN takes at least 16 characters (or random) - docs: EDITOR_TOKEN only for an editor started directly, the CSP in the plugin guide, the removed v2 changelog - breakout: revert stray edits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4fa9f45 to
0c4fb10
Compare
|
Thanks for the review. Addressed in 6acab04 ( Fixed
Not changed
Checked at the top of the stack: lint, typecheck and unit tests pass, and e2e passed 109/109. Not covered, as you noted: Monaco and the plugins under the hosted policy. |
What does this PR do?
Handle online mode.
How do you test this PR?