Core concepts
Permissions
Every capability an app has is declared in its manifest, approved by the user at install time, and re-checked by the host on every single SDK call. This page is the complete model.
The permissions object
Four keys, all optional. Anything else you add is ignored with a warning, so a typo silently removes a capability — check spelling carefully.
{
"permissions": {
"read": ["home/Documents", "home/Pictures"],
"write": ["home/Documents"],
"ai": true,
"network": ["https://api.example.com/v1/"]
}
}| Field | Type | Description |
|---|---|---|
| read | string[] | Home-relative directories the app may read. [] grants only the private sandbox. |
| write | string[] | Home-relative directories the app may write, create files in and delete from. [] grants only the private sandbox. |
| ai | boolean | true enables window.vos.ai. Anything else disables it. |
| network | string[] | Allowed https:// origins, optionally with a path prefix. Up to 32 entries. |
Presence vs. value
The most important rule in the model: the presence of a key declares the capability; its value scopes it.
| Declaration | Result |
|---|---|
key omitted | Capability off. Every related SDK call rejects with “app lacks … permission”. |
"read": [] | Capability on, scoped to the app's private sandbox only. No user prompt for shared folders. |
"read": ["home/Documents"] | Capability on, sandbox plus the listed folders — the user approves them at install. |
"network": [] | Capability declared with no targets — every request is blocked. Omit the key instead. |
Nothing is granted implicitly
Requesting write does not imply read. Most apps need both keys.
Filesystem scopes
Filesystem access is directory-scoped. A grant on home/Documents covers that folder and everything beneath it, but nothing above it.
// The most common configuration: the app stores its own data and
// nothing else. Grants home/.apps/<appId>/ with no directory prompt.
"permissions": { "read": [], "write": [] }- Private sandbox:
home/.apps/<appId>/— always available once the capability is declared. Read the exact path fromapp.getInfo().sandboxPath. - Shared folders: use these only when the user should see and manage the files with other apps — documents, images, exports.
- No traversal: paths containing
..are rejected outright.
// A game, a calculator, a clock: no capabilities at all.
// Omit the key entirely — every window.vos.fs / ai / net call is rejected,
// but window.vos.app.getInfo() still works.
{ "vibe": 1, "name": "Calculator" }Reserved paths
Three areas are owned by the operating system. They are stripped from any allow-list and blocked at call time even if a grant somehow contains them:
| Path | Why |
|---|---|
home/.system | OS state and configuration. |
home/.Trash | Deleted items belong to the user and the OS. |
home/.apps | Every app's private data. You reach your own sandbox through sandboxPath and can never read another app's. |
AI
"ai": true enables window.vos.ai. The user approves it at install time alongside filesystem scopes, and the actual model usage is charged against their plan — you need no keys and no billing of your own.
Permission is not entitlement
Having the permission does not guarantee AI works: the user may be signed out, on a plan without AI, or out of credits. Check with ai.canUse() and always handle ai.chat rejections.
Network
Each entry in network is an https:// origin with an optional path prefix; the host builds a CSP from the list and re-validates every net.fetch URL against it. Wildcard hostnames, credentials in URLs, and private or internal addresses are never allowed. Full details in the vos.net reference.
Install & upgrade
- At install the user sees exactly what you requested and approves or declines. Sandbox-only apps install with no directory prompt.
- Publishing an update that widens the scope re-prompts the user on upgrade. Until they accept, the previous grant applies — so ship capability checks, not assumptions.
- Users can revoke network origins after install from Web Apps → app details → Permissions.
- The manifest inside your HTML is never trusted at runtime; the host always reads the stored grant. There is no SDK call that changes permissions.
Read what was actually granted at boot and adapt:
const { permissions } = await window.vos.app.getInfo();
// { hasRead: true, hasWrite: false, hasAi: false, hasNet: true, network: [...] }
if (!permissions.hasWrite) enterReadOnlyMode();Common configurations
| App type | Permissions |
|---|---|
| Game, calculator, clock — no data at all | omit permissions |
| Notes, to-do, settings-heavy app storing its own data | { "read": [], "write": [] } |
| Editor working on the user's documents | { "read": ["home/Documents"], "write": ["home/Documents"] } |
| Image viewer — reads photos, saves nothing shared | { "read": ["home/Pictures"], "write": [] } |
| AI writing assistant with local drafts | { "read": [], "write": [], "ai": true } |
| API-backed client with an offline cache | { "read": [], "write": [], "network": ["https://api.example.com/"] } |
Least privilege wins reviews
Reviewers compare what you request against what your code actually does. An unused home grant or an unused AI flag is a common reason changes are requested. Ask for the minimum, and explain in your description why you need anything broad.
Legacy formats
Very old manifests declared permissions as a string array. The host still accepts these and converts them, with a warning to migrate.
// Deprecated string form — still accepted, migrate when you next update.
"permissions": ["fs.home"] // → read: [], write: []
"permissions": ["fs.read"] // → read: []
"permissions": ["fs.write"] // → write: []
// Hard error — this one is no longer valid at all:
"permissions": ["fs.documents"]"fs.documents"is rejected with a hard error — replace it with the structured object.- Unknown keys inside
permissionsare ignored with a warning; a wrong type (for example a string where an array is expected) rejects the manifest.