Repository navigation
EN Clever Security
Français · Type: explanation + reference · Audience: admin · Context: hosted Studio · Status: ready · Applies from: 4.2.3
The Studio Server (hosted team Studio) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Postgres, S3 and Keycloak in CI; Chromium and Firefox (job studio-hosted); WebKit on macOS, by hand only · automated checks: jobs
studio-hosted,studio-install-smoke; CI planstudioHosted; 3 test files · people's passes:studio-hostednot run (#149),studio-collabnot run (#150),studio-previewnot run (#166),studio-assets-hostednot run (#167),studio-mcp-remotenot run (#196),studio-oidc-genericnot run (#197) · known limits:worker-network
How a hosted Studio on Clever Cloud decides who a request is, what it may do, how fast a removed member loses access, and why the game's code never touches a secret. Each rule here is read from the current code; its limits are said.
A shared Studio holds a team's work and runs code a member wrote. Three dangers shape it: a page of another site acting with a member's session (CSRF), a member who should no longer get in, and the game's own code reaching the Studio's secrets. The provider says who someone is; the Studio decides what they may do (ADR 0027).
Each request gets exactly one policy, chosen by the request alone; never two identities on one request.
| Policy | When | Rules |
|---|---|---|
| A, cookie | an API request with no Authorization header |
The cookie __Host-ws_session (Secure, HttpOnly, SameSite=Lax); only its sha256 is stored. A mutation (POST, PUT, PATCH, DELETE) needs Origin equal to publicUrl, X-Studio-Request: 1 and a JSON body; a refusal says only forbidden (403) |
| B, Bearer | an Authorization header |
An access token (a JWT) for the configuration's audience, 5 min by default (15 at most); cookies ignored. An id_token, another audience, a longer token, a non-member, a foreign Origin: refused |
| C, callback | GET /auth/callback |
The provider's answer to a login begun server-side (Authorization Code, PKCE S256, state, nonce; the pre-authentication used once, 10 min) |
| D, back-channel logout | POST /auth/backchannel |
A form-encoded logout_token, checked per the specification; a replay refused by its jti
|
| E, login and logout |
GET /auth/login, POST /auth/logout
|
The login's return path is relative only, never another site |
| F, the game's origin | any request on playUrl
|
A ticket from a member's browser session opens a game session (cookie Secure; HttpOnly; SameSite=None; Partitioned); without it, 401 "open the game from the Studio", before the path is read. No API, no /auth, no /metrics there |
Roles live in the Studio: reading needs a reviewer, writing a dev, the members and the audit log an owner. The last owner can be neither demoted nor removed.
| Event | When access ends |
|---|---|
| A member removed or revoked in the Studio | At their next request: sessions deleted, every token issued before refused (revoked_before); their long connections (live stream, MCP) close within 5 s, their leases are released |
| A back-channel logout from the provider | At once: with a sid, that provider session only; with a sub alone, every session and token of the user |
| Disabled at the provider, no back-channel | At the session's next check: every 5 min, by its refresh token; a definitive refusal ends it |
| The provider unreachable | The session lives until R (15 min) after its last confirmation, then is suspended (503, fail closed) until a check succeeds |
| A provider that gives no refresh token | Sessions of R at most |
| A refresh token that no longer opens (its key left the ring) | The session ends: log in again (Rotating keys) |
Browser sessions also end after 8 h idle or 24 h in all (sessions.idleHours, sessions.absoluteHours).
Guarantee ·
provider-unreachable-suspends· referenced · scope self-checked by the author (AI), not reviewed When the provider cannot be reached for longer than R, a session is suspended: the Studio answers 503 rather than serving it unchecked, and the session comes back once the provider answers a check. The test runs on a simulated clock, against a test provider.
The game's code (its validator, solver, previews, migration, asset tools) runs only on studio-worker, a second app
(ADR 0028). On Clever Cloud's Node.js runtime there is one system user, and a child process can read its parent's
environment through /proc (spike S §1): so the worker holds nothing worth stealing.
- It has no database or Cellar credentials (never link the add-ons to it) and no private key: only
WORKER_PUBLIC_KEY, to check that a job was signed by the Studio (Ed25519), for itsWORKER_INSTANCE, and not expired (a job lives at most its limit plus 30 s of clock skew). - A job reads its snapshot and writes its result through presigned S3 URLs, one key and one duration each, in a staging prefix of its own.
- Its child starts with an empty environment (measured: 0 variables, spike S §6). For the engine upgrade and the sheets' cutting, the Studio refuses a result that names a file it did not ask for.
Limit ·
worker-networkThe worker's outbound network is not restricted: the game's code running there could reach the Internet. Running the worker without outbound network was not tried (QUALIF-4.2.3 §7).
The server believes X-Forwarded-For and X-Forwarded-Proto only from the addresses in trustProxy; with OIDC and
listening beyond the loopback it refuses to start without them. That is how two members behind Clever's proxies are
told apart for the rate limits (the /auth/ routes: 30 requests a minute per address, per instance, in memory; then
429). The private ranges of the example are an assumption to narrow (Configuration).
Read once from <NAME>_FILE (0600) or the environment, then removed from the process's environment; never logged.
Refresh tokens and id_tokens are sealed with AES-256-GCM under the STUDIO_SESSION_KEY ring. A backup is trusted
input (it can add owners). The full list: Configuration.
With publicUrl and playUrl on two subdomains of one domain, the Studio and the game are one site and the game's
frame keeps its cookie in every browser. Two *.cleverapps.io names are two sites, where the cookie survives only as a
partitioned one (CHIPS), which WebKit on Linux does not keep: Domains and TLS.
- Rights in the provider (groups). The maintainer chose the Studio on 17 October 2026: the provider holds no right.
- Token introspection (RFC 7662) instead of short JWT access tokens: the documented option if a provider cannot issue short tokens.
- Running the game's code in the Studio's process: it would see the Studio's secrets.
- Rate limits are per instance and in memory: a restart resets them.
- A disabled user keeps access up to 5 min without back-channel logout, up to R when the provider is down.
- The
studio-hostedpass (a real Safari, Clever's actual proxy ranges) has not been made for 4.4.0.
- ADR 0027, identity, sessions, tokens; ADR 0028, where the game's code runs; the threat model.
- The code:
studio-server/src/server/auth/gate.ts,studio-server/src/server/auth/routes.ts,studio-server/src/server/play.ts,studio-server/src/worker/job.ts. - The test behind the guarantee:
tests/studio-auth.test.ts.
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits