e4b0419a53cde8a6ed3aa09fb3749e8019a7d678
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cfd6d7a8df |
Lock the server down before the editor goes public (#427)
The editor is about to be opened to strangers: a post on Reddit and a guest access (#428). nginx served the whole workspace and ran every PHP file in it, the API let anybody guess passwords as fast as they liked, and one account could fill the disk. - nginx (shared web stack, outside this repo, backup in /root/config-backup-20261015/) hands out only the two pages, dist/, public/brand/, the uploads by kind and ending, and api/index.php. Everything else under /feuerwerk/ is a 404. - PHP behind it has display_errors off and no X-Powered-By. Plain HTTP forwards to HTTPS, the API refuses it. - Headers: a Content-Security-Policy without inline code or eval, nosniff, frame-ancestors, HSTS, Referrer-, Permissions-, COOP, CORP. - vite.config.ts writes the same policy into the built pages, so CI runs the whole suite under it. Pixi gets by without eval through pixi.js/unsafe-eval. The page CSS left its inline style for a file. - The harness fails a module for anything the policy refuses, from Chrome's log and from the page's securitypolicyviolation event. - LoginThrottle: five wrong passwords from one address within 15 minutes, and it gets 429 until 15 minutes after the last of them. The address is the last X-Forwarded-For entry, the Brandmauer's own. - Quota: 1 GB per account for uploads, projects and snapshots. Only a write that grows the account is refused (413). Admins have no limit. - A write whose Sec-Fetch-Site says another site is refused (403). - The session cookie carries Secure, nginx passes HTTPS on. - showFailure puts the message in as text, not as HTML. - Every PHP script outside api/ stops unless it runs on the command line. - The video worker sees the checkout read-only and can only write data/videos. No capabilities, 2 GB and 256 processes at most. - tests/php/rights.test.php runs the API under php -S on a scratch directory and asks every route as signed out, owner, stranger and admin. A route named in api/index.php but missing in the matrix fails it. limits.test.php covers the brake and the quota. Findings: - tmp/ was readable from outside, with 79 copies of project files. Closed on 15.10. at 20:02 with chmod 750. The log back to 11.10. shows only our own requests. - Port 8080 is bound on 0.0.0.0, so the LAN could reach nginx past the Brandmauer. Left as it is: the web stack is not this project's. - npm audit finds nothing in what is shipped. Dev tools (vitest, brace-expansion, source-map-js) have advisories, not touched here. Tests: check 313 files / 4843 tests. PHP limits 21, rights 60, accounts 56, snapshots 15, releases 20, files 46 (videos 50/51, no ffmpeg in the web container). e2e on stein2, atx was off: 34+38+41 197/198, the one red check (a click in 41) passes with 41 alone, 27/27. 67 misses frames at 1080p on stein2 with and without this. Path list from outside: 10 served, 26 refused. ZAP baseline against the live site: nothing High or Medium under /feuerwerk/. Counter-checks: rights.test.php without the cross-site guard, the quota, the brake, the admin check of users, or with an undocumented route fails each time. A policy without wasm-unsafe-eval fails e2e 41 with the harness's own CSP line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4194f3e150 |
Share a link that plays the game without the editor (#266)
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|