Build an app
Secure sandbox
Every VibeOS app runs fully isolated and can only touch the system through a narrow, host-enforced gateway. Here's exactly what keeps apps — and your users — safe.
The security model in one line
An app is untrusted HTML running inside a locked-down iframe. It has no ambient authority: the only way out is the window.vos SDK, and every call it makes is re-checked by the host against a grant the app cannot see or change. Capability is opt-in, scoped, and revocable.
Isolation guarantees
The app document is rendered in a sandboxed browser frame, separate from the desktop and from every other app. Within that frame:
- No access to the host page. The app can't read the VibeOS desktop DOM, cookies, storage, or other apps' windows.
- No external network for core logic. Apps can't load scripts, styles, or libraries from external URLs — all CSS and JavaScript must be inlined in the single
htmlfield, so there's no hidden phone-home channel. - No raw filesystem or OS handles. There is no
fs, no native APIs — only the brokeredwindow.vossurface. - A private home by default. Each app gets its own sandbox directory at
home/.apps/<appId>/that no other app can reach.
One file, no runtime fetch
The brokered SDK boundary
The app never performs privileged work itself. It asks the host to, by calling a window.vos method. Each call is a message across the sandbox boundary; the host validates it, performs the action with its own authority, and returns a result. The app holds no keys and no file handles — it only holds promises.
// Every call crosses the boundary and is re-checked host-side.
// The app cannot widen its own scope — the grant lives outside the iframe.
await window.vos.fs.writeFile("home/Documents", "note.md", text);
// host: is "home/Documents" inside the installed write grant? ✔ allowed
await window.vos.fs.readFile("home/Pictures", "secret.png");
// host: "home/Pictures" not in read grant -> rejected, Promise rejects
await window.vos.fs.readFile(".system", "keys.json");
// host: reserved path ✗ always rejected, regardless of grantPermission structure
Permissions are declared in the manifest and are directory-scoped for the filesystem and a simple capability flag for AI.
{
"permissions": {
"read": ["home/Documents"], // directories the app may read
"write": ["home/Documents"], // directories the app may write
"ai": true // optional AI gateway access
}
}| Field | Type | Description |
|---|---|---|
| read | string[] | Home-relative directories the app may read. Presence of the key (even []) declares the filesystem read capability. |
| write | string[] | Home-relative directories the app may write. An empty [] means sandbox-only access at home/.apps/<appId>/. |
| ai | boolean | Gates the AI gateway (window.vos.ai). When omitted or false, every AI call is rejected. |
Declaring vs. granting
The manifest declares what an app wants. The user grants it at install time. These are different things:
- The presence of
read/writedeclares the filesystem capability; the array contents declare which directories. - An empty array (
[]) declares filesystem use but scopes it to the app's private sandbox only — no shared directories, no prompt. - Requesting a shared directory like
["home/Documents"]triggers an install-time approval prompt the user must accept. ai: trueis approved at install alongside any filesystem scopes.
Reserved paths
These are blocked for every app, no matter what the manifest requests:
.system— OS internals..Trash— deleted items.- Other apps' sandboxes at
.apps/<id>. - Any hidden/dotfile path and
..traversal segments — rejected by the manifest validator before the app is ever accepted.
How enforcement works
Declaration is not trust. The grant is stored host-side and consulted on every single call:
- Path is normalised first. The host resolves and canonicalises the requested path, so tricks like
home/Documents/../Picturescan't escape the grant. - Checked against the installed grant. Read calls require a covering
readentry; write/delete calls require a coveringwriteentry; AI calls requireai. - No widening at runtime. The app can't edit its own manifest or grant — the only way scope changes is a new install/upgrade the user re-approves.
- Fail closed. Anything not explicitly allowed is denied, and the corresponding
window.vospromise rejects.
The manifest is a request, never a guarantee
["home"] still only gets what the user approved. Even with a grant, every call is validated again at the moment it runs — there's no cached "trusted" state an app can exploit.Review backs the runtime
Runtime isolation is paired with up-front review. Before an app is published, an automated AI pass plus a human reviewer check the manifest and inline code for over-broad permissions, undisclosed behaviour, and unsafe patterns — see submission & review. Combined with the sandbox, this means users approve a known, scoped, immutable app.
Build with least privilege
ai if you don't call it, and prefer the private sandbox. See the manifest reference and the SDK reference.