Sandbox and permissions
Every game on One Game a Day runs under the same isolation rules. They are applied in three layers, all driven by the same per-game permissions:
- The
sandboxattribute on the frame that holds the game. - A content security policy sent with the game's HTML documents.
- A permissions policy that switches device features off.
This page describes what those layers allow, what they block, and what a game can ask to have switched on.
What every game can do
Without asking for anything, a game can:
- Run scripts, including inline scripts,
evaland WebAssembly. - Draw with Canvas and WebGL, and play sound with Web Audio and media elements.
- Start web workers from its own files or from
blob:addresses. - Load any file from its own folder, and use
data:andblob:addresses. - Read keyboard, mouse and touch input while it has focus.
- Use a connected gamepad, enter full screen and request autoplay. These three browser features are delegated to the game's frame. A browser may still wait for a click, tap or key press inside the game before it lets sound start.
- Store data in the browser under its own origin. See Saves and storage.
What no game can do
These limits apply to every game and cannot be lifted by any permission.
- Reach the platform. The game is on a different origin from the site. It cannot read or change the page around it, the visitor's favourites or history, or anything belonging to another game.
- Navigate the top-level page. The frame is never given that ability. A game cannot send the visitor away from the site.
- Use sensitive device features. The following are denied outright: camera, microphone, geolocation, screen capture, accelerometer, gyroscope, magnetometer, USB, serial ports, HID devices, MIDI, payment requests, XR spatial tracking and browsing topics. Tilt controls and webcam or microphone input are therefore not available.
- Embed plugins.
objectandembedcontent is blocked. - Be framed elsewhere. A game's documents may be framed only by the platform.
Two more things are unreliable in the sandbox, and the validator warns about them:
- Service workers. A sandboxed game cannot rely on one. Registration is ignored or fails.
- Cookies. Use
localStorageinstead ofdocument.cookie.
Permissions a game can have
Everything else is off by default and controlled by the permissions object in game.json.
| Permission | Default | What it unlocks |
|---|---|---|
network | No origins | Loading from, and connecting to, the listed https origins. |
pointerLock | On if the entry page uses it | Capturing the mouse pointer, for mouse-look controls. |
forms | On if the entry page uses it | Submitting HTML forms to the game's own origin. |
downloads | On if the entry page uses it | Offering a file to download, such as an exported save. |
modals | Off | The blocking dialogs alert, confirm and prompt. |
popups | Off | Opening links in a new tab. |
Granted automatically
Three permissions are switched on by the platform when it sees that a game uses the feature:
pointerLock, when the code callsrequestPointerLock.forms, when the page contains aformelement.downloads, when the code sets adownloadattribute.
This detection reads the entry HTML file only. If the relevant code is in a separate script file, set the permission to true yourself. You can also set any of the three to false to keep it off.
Granted only on request
Three permissions are never inferred. They must be set explicitly, and since games are added by the team, they are a matter to agree with the team.
network. By default a game has no network access beyond its own files. network is a list of up to twelve https origins, each without a path:
{
"permissions": {
"network": ["https://api.example.com"]
}
}
Each listed origin is added to the game's content security policy for scripts, styles, images, media, fonts and connections. Anything not listed stays blocked, in the browser, whatever the game's code attempts.
A reference in the game's HTML or CSS to an origin that is not listed is a validation error. See Game package format.
The best practice is not to need this permission at all. A game with no outside requests works offline, loads faster and cannot break because another service changed.
modals. Blocking dialogs freeze the page while they are open, so they are opt-in. Without the permission, calls to alert, confirm and prompt do nothing. Drawing your own dialog inside the game needs no permission and behaves better in a frame.
popups. Without it, window.open and links that target a new tab do nothing. With it, a game can open a link in a new tab, and the page opened there is not sandboxed.
The default policy
For a game with no permissions, the content security policy sent with its HTML documents is:
default-src 'self' data: blob:;
script-src 'self' 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval' data: blob:;
style-src 'self' 'unsafe-inline' data: blob:;
img-src 'self' data: blob:;
media-src 'self' data: blob:;
font-src 'self' data: blob:;
connect-src 'self' data: blob:;
worker-src 'self' blob:;
child-src 'self' blob: data:;
object-src 'none';
base-uri 'self';
form-action 'none';
frame-ancestors https://onegameaday.net
'self' here is the game's own origin. Origins listed under network are appended to the first seven directives, and form-action becomes 'self' when forms is granted.
The frame itself always has allow-scripts and allow-same-origin. It gains allow-pointer-lock, allow-forms, allow-downloads, allow-modals or allow-popups only when the matching permission is granted.
Saves and storage
Each game is served from its own origin, https://<slug>.onegameaday.net, and its frame keeps that origin. This is what makes saving work.
localStorageworks, and is the recommended place for saves. All five launch games save this way. The browser's other origin-scoped storage follows the same rules.- Storage is private to the game. No other game and no part of the platform shares the origin, so nothing else can read or overwrite a game's data, and the game does not share its
localStorageallowance with other games. - Storage survives updates. Storage belongs to the origin, and the origin comes from the slug. A new build of the same game sees the data its earlier builds wrote, so keep save formats backward compatible.
- Storage is local to the player. Saves live in one browser on one device. The platform keeps no copy and offers no cloud saves or sync.
- Players can lose it. Clearing browser data, private browsing windows and the browser's own clean-up all remove stored data. Handle a missing or damaged save without failing.
How the frame behaves
A few practical details of the player are worth knowing when you build.
- Size. On the game's page the frame has the game's
aspectRatio, limited by the height of the window. In full screen, and in the full-window view used on phones, it fills the available space whatever the ratio. Size the game to its window and respond toresizeevents. - One resize after load. The player lays the frame out before the game's document can run, and sends the game one real
resizeevent just after it loads. A game that measures its viewport only once at start-up still gets a correct size. - Focus. The player moves keyboard focus into the frame when the game has loaded. Visitors may click elsewhere, so do not assume the game keeps focus; pausing when focus is lost is good behaviour.
- Restart. The Restart button discards the frame and loads the entry page again. Anything not saved by then is gone.
- No referrer and no scrolling. The frame is created with a no-referrer policy and without scrollbars.
- No messages. The platform has no SDK and no message channel. It does not send messages to a game and does not listen for any.
- Plain requests only. A game's own origin answers
GETandHEADrequests for its files. There is no server-side component for a game to post to. - No cross-origin isolation. The headers that
SharedArrayBufferneeds are not sent, so features that depend on it are unavailable.
How play is counted
The platform measures active play from outside the frame: time during which the tab is visible, the window has focus and focus is inside the game. A game does not need to report anything, and cannot. See How Most played and Trending are calculated.