Repository navigation
EN Clever Security
Français · Explanation + reference · Published, partly reviewed by a person · Applies from: 4.2.3
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.
Audience, context, and the statuses of what this page rests on
Audience: admin · Context: hosted Studio
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
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 · executed on301f199dc2e3· scope 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 Chromium, Firefox and WebKit on macOS, where the hosted e2e checks it (not WebKit on Linux;
a real Safari is not checked yet). 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.5.5 r1, documentation revision · révision de la documentation 301f199dc2e38aa35a552a22ef87fd1e0f1f50f7.
Latest documentation · dernière documentation: https://quest-n-click.cleverapps.io/docs · https://quest-n-click.cleverapps.io/fr/docs; earlier versions · versions précédentes: each release's quest-n-click-docs-<version>.tar.gz · l'archive quest-n-click-docs-<version>.tar.gz de chaque release, https://github.com/Quest-n-Click/quest-n-click/releases
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 (18)
- Requirements
- Install from the release archive
- Install from npm
- 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.5
- 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