Back to Cherry Studio

Sandbox

docs/references/mini-app/sandbox.md

2.0.107.0 KB
Original Source

Sandbox

Read this before anything else. A mini app is a web page, but it does not run where a web page usually runs: it has an opaque origin, no network, and no browser permissions. Code that "works fine in Chrome" fails here in ways that look like bugs in your code.

The environment

PropertyValue
URLcherry-miniapp://<appId>/<path>; / serves index.html, otherwise the path is a file in your package
OriginOpaque — CSP sandbox allow-scripts is applied to every response. location.origin is "null"
Node / ElectronAbsent. require, process, ipcRenderer do not exist; the only host surface is window.cherry
Content Security Policysandbox allow-scripts; default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' data: blob:; connect-src 'none'; frame-src 'none'; worker-src 'none'; object-src 'none'; base-uri 'self'; form-action 'none'
Network from the pageEvery request that is not your own package is cancelled before it leaves the process, regardless of the manifest
Chromium permissionsEvery permission request (Notification, geolocation, camera, microphone, clipboard, MIDI, USB, Bluetooth, screen capture) is denied

Blocked, and what to use instead

You wroteWhat happensUse instead
localStorage, sessionStorageThrows SecurityError — opaque origins have no storagecherry.storage
indexedDB.open(...)Rejects — same reasoncherry.file for blobs, cherry.storage for state
document.cookie, Cache API, caches.openNo-op / rejectscherry.storage
fetch('https://api.example.com'), XMLHttpRequest, WebSocket, EventSource, navigator.sendBeaconBlocked by connect-src 'none' and by the host's request filter — even for hosts in manifest.networkcherry.network.fetch (https, declared hosts, request/response ≤ 1 MB / 5 MB)
<script src="https://cdn...">, <link href="https://...">, ``BlockedBundle the asset into the package
new Worker(...), SharedWorker, navigator.serviceWorker.registerBlockedRun on the main thread, or inline the work
<iframe>, <embed>, <object>Blocked (frame-src 'none', object-src 'none')Render in the page
<webview>Denied by the main process, and webviewTag is off for your page whatever the host window enables. Not a CSP matter — an Electron <webview> is not a browsing context frame-src governs, so it would carry neither this page's CSP nor its request filterNothing
window.open, <a target="_blank">Denied — no popup is createdNothing. There is no "open in browser" in this release
<a download>, URL.createObjectURL(blob) + click, navigating to a downloadCancelled — no save dialog appearscherry.file.export
showOpenFilePicker, showSaveFilePicker, showDirectoryPickerReject — the File System Access permission is denied<input type="file"> to read, cherry.file.export to write
location.href = 'https://...', <form action>Navigation outside cherry-miniapp://<appId>/ is cancelledNavigate within your package only
WebRTC (RTCPeerConnection)UDP is blocked and TURN/TCP is routed to a dead proxy — connections never establishNothing
Notification.requestPermission()Always deniedcherry.notification.show
navigator.clipboard.*Rejects — the clipboard permission is deniedcherry.clipboard, while the app has keyboard focus
navigator.language, languagechangeFrozen at load; never updatescherry.app.getInfo().locale and cherry.on('app.localeChange', ...)
document.visibilityState, visibilitychangeNever changes while the app sits hidden in the keep-alive poolcherry.on('app.visibilityChange', ...)
beforeunload, pagehide, unloadMay never fire — the app can be destroyed without noticeSave on every change; see Lifecycle

What works

FeatureNotes
Inline scripts, eval, new FunctionAllowed by script-src
WebAssembly.wasm is served as application/wasm; 'unsafe-eval' covers compilation
fetch('./assets/level.json')Your own package is fetchable — see below
data: and blob: URLsFor images, media and fonts you generate at runtime (URL.createObjectURL)
Canvas, WebGL, WebGPU, Web AudioStandard browser features with no network dependency
history.pushState, hash routingSame-origin navigation within the package is allowed
matchMedia('(prefers-color-scheme: dark)')Follows the user's Cherry theme, including changes
<input type="file">, dropping a file onto the pageYou get the File — contents and name, never a path. Handle dragover / drop with preventDefault() as on any page
Pasting into your inputsThe keystroke works; reading the clipboard programmatically is cherry.clipboard.read

Fetching your own package files

Because the document's origin is opaque, even a request to your own package is cross-origin. The host serves every package response — including 404 and 403 — with Access-Control-Allow-Origin: * so that:

  • fetch('./data.json') resolves;
  • a missing file resolves to a Response with status === 404 rather than throwing TypeError: Failed to fetch. Check response.ok.

Files are served with a content type derived from the extension (.html, .js, .css, .json, .svg, .png, .jpg/.jpeg, .gif, .webp, .woff2, .wasm); anything else is application/octet-stream. Paths are resolved inside the package after following symlinks; anything that escapes is a 403. The host bounds concurrent package reads per app (8 active, 64 queued; the 73rd concurrent request fails), so do not issue hundreds of parallel fetch calls for large assets.

/__cherry/* is reserved for host assets — today only /__cherry/theme.css (Theming). A package containing a top-level __cherry directory is refused at install.

Multiple instances

The same app can run in more than one window at once (the user can detach a tab). Each instance is a separate page with its own JavaScript state, but cherry.storage and cherry.file are shared per app — the last write wins. callIds for cherry.ai are scoped per instance.

Keyboard

Every keystroke is yours. While your app has focus, Cherry's own shortcuts — print, save, the global keybindings — do not fire: the host's key relay preload is not loaded for local apps, because a sandboxed preload must be a single bundled file and the capability bridge already occupies that slot. Do not rely on the host answering any key on your behalf, and prefer not to bind the platform-standard combinations users expect Cherry to handle.

Debugging

DevTools are available on the webview from the host's mini app UI. Blocked requests appear in the Network panel as (blocked:csp) or cancelled; the CSP is legible in the response headers of any package file.