Skip to content

fix: an escape hatch for the stream gate until RomM sends the token - #22

Merged
LoneAngelFayt merged 5 commits into
mainfrom
dev
Aug 3, 2026
Merged

fix: an escape hatch for the stream gate until RomM sends the token#22
LoneAngelFayt merged 5 commits into
mainfrom
dev

Conversation

@LoneAngelFayt

@LoneAngelFayt LoneAngelFayt commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Description

The stream gate added in 8e3777b locks out every user of released RomM. This adds the switch that turns enforcement off, defaulted to off until upstream catches up.

The lockout

The gate requires a session token that POST /launch mints and RomM is supposed to append to the iframe host URL. RomM does not do that yet. rommapp/romm#3211 is merged and is what people are running, and its claim response returns "host": container.get("host", ""), the operator's configured URL with nothing appended. The half that carries the token through is rommapp/romm#3856, still open.

So the claim does reach POST /launch and the broker does mint a token, but the browser then loads the stream host bare. nginx sends the subrequest to /verify, _check_stream_token("") answers "no stream token in the request", and every request is refused: the document, every asset, and the WebSocket upgrade alike. A black stream with nothing on screen to say why, for anyone who pulls :latest.

The switch

STREAM_GATE, values off and token, defaulting to off. An unrecognized value warns and resolves to off rather than tracking the default, because a typo must fail toward a reachable stream and never toward one nobody can open.

Enforcement is decided in the broker, not in the nginx config, and that placement is the point. The nginx auth_request injection is untouched and still gates every server block. As 1736c84 recorded, that patch lands in the container's writable layer behind its own marker grep, so a gate decided there would need a --force-recreate in each direction; decided in the broker, it is one environment variable.

The token machinery is left alone: /launch still mints, /status still reports, release still clears, and the TTL and grace window still tick. Only enforcement is bypassed, so turning the gate back on when #3856 merges is one environment variable and a docker compose up -d.

Reporting, so the live mode is never a guess

Running with the gate off is a real exposure, so the broker states it rather than leaving it to be deduced. It logs the active mode at startup, WARNING when off (naming what is exposed and how to close it) and INFO when enforcing, and GET /status reports it as stream_gate.

Accepted risk, stated plainly

Defaulted off, :latest serves the interactive desktop with the ROM library browsable at /files, which is the state before 8e3777b. That is the lesser harm against locking out every user, and the README says so rather than burying it. Keep the container on a network you trust until #3856 ships.

Reverting to a gated default is written up in the design doc: change STREAM_GATE_DEFAULT, update the one test that guards it, update the README, and tell operators on an older RomM to set STREAM_GATE=off.

Review note

A whole-branch review caught one real defect after the feature was otherwise finished, fixed in 563d241. The migration was documented as docker compose restart, which replays the environment baked in at create time. An operator following it would have set STREAM_GATE=token, watched the container come back, and believed the desktop was closed while it stayed wide open. It is docker compose up -d, and no --force-recreate is needed because the changed variable recreates the container on its own and the init re-injects the same gate on the fresh layer.

Checklist

  • I've tested the changes locally
  • I've updated relevant comments
  • I've added unit tests that cover the changes

The gate 8e3777b added locks out every user of released RomM. Upstream
rommapp/romm#3211 is merged and is what people run; its claim response
returns the operator's configured host with nothing appended. The RomM
half of the gate, which carries the stream token into the iframe URL,
is rommapp/romm#3856 and is still open. So the browser loads the stream
bare, /verify sees no token, and the document, every asset and the
WebSocket upgrade are all refused.

Specs STREAM_GATE=off|token, defaulted off until #3856 merges. Deciding
enforcement in the broker rather than in the nginx config keeps a mode
switch to a restart: as 1736c84 recorded, the nginx patch lands in the
writable layer behind its own marker grep, so a gate decided there would
need a recreate in each direction.

Also records the accepted risk. Defaulted off, :latest serves the
interactive desktop with the ROM library mounted again, which the README
will state plainly rather than bury.
Four tasks, each with its own test cycle: resolve the mode, bypass
enforcement, report which mode is live, then correct the docs that
currently promise a gate the default no longer enforces.

Records two things the spec could not know. The constant cannot sit
beside STREAM_TOKEN_GRACE as specced, because resolving it warns and log
does not exist that early in the module, so it goes after the logger.
And the seven existing gate tests have to be pinned to STREAM_GATE=token
before the bypass lands, or every 403 assertion in them starts passing
for the wrong reason under the new default.

Container verification is spelled out too, since the suite cannot reach
it: the lockout being gone is a 200 from 3001 with no token, and the
restart-not-recreate claim is worth checking directly.
Parsing only, nothing enforces it yet. An unrecognized value resolves to
'off' rather than to whatever the default happens to be, because the
failure this switch exists to prevent is a stream nobody can reach and a
typo must not be able to reintroduce it.

Declared after the logger rather than beside STREAM_TOKEN_GRACE with the
other stream constants: resolving the value warns, and log does not exist
that early in the module.
8e3777b gates the desktop on a token RomM cannot send. rommapp/romm#3211
is merged and is what people run, and its claim response returns the
operator's configured host with nothing appended; the half that carries
the token is rommapp/romm#3856, still open. So the browser loads the
stream bare, /verify sees no token, and the document, every asset and the
WebSocket upgrade are all refused. That is a lockout, not a gate, and it
is live for everyone on the published image.

The decision is made here rather than in the nginx config on purpose. As
1736c84 recorded, that patch lands in the container's writable layer
behind its own marker grep, so a gate decided there needs a recreate in
each direction; decided in the broker, switching modes is a restart.

The token machinery is untouched: /launch still mints, /status still
reports, release still clears, and the TTL and grace still tick. Only
enforcement is bypassed, so turning the gate back on is one variable.

The seven existing gate tests are pinned to STREAM_GATE=token, or every
403 assertion in them would have started passing for the wrong reason
under the new default.
.releaserc.json runs semantic-release off main, where feat: cuts a minor
version. This branch is a bugfix for a lockout that is live in the
published image, so it takes a patch, and the one feat: subject already
on the branch has been retitled to match.

Tasks 3 and 4 carry their commit messages in the plan text, which the
task briefs are extracted from verbatim, so the convention has to change
here or the next two implementers would reintroduce it.
@LoneAngelFayt
LoneAngelFayt merged commit cf6ef3b into main Aug 3, 2026
3 checks passed
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.4.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant