General Text & Computing Company

Security & the App Sandbox

Security & the app sandbox

General Text runs apps you didn't write: forked from the gallery, pushed by an agent, or your own. So the platform makes apps self-contained by construction: an app depends on nothing but your files and the runtime, and reaches only the network hosts its manifest declares (most declare none).

An app can write only its own data folder, read Shared Files and nothing else in the workspace, and reach only the network hosts it declared in its manifest. That list is shown before you fork it. An app that declares nothing has no network at all: it can't phone home or pull in third-party code.

This is first a durability promise: because an app calls no outside service, it keeps working offline and for as long as you keep the files, even if we disappear. It is a privacy promise too: an app with no network has nothing to leak. This holds for first-party apps as well; we hold ourselves to the same boundary, so "it's just an app" always means the same thing.

What an app can and cannot do

  • Can: read and write files in its own data folder, _gtApps/{app}/data/, and live-sync them. This is its default and needs no permission.
  • Can: read Shared Files, everything in the workspace that isn't an app's folder or platform config. Every app gets this, read-only: apps read the workspace's files, people write them.
  • Cannot: write Shared Files, or touch another app's folder, or its own record and code, which sit next to data/ and are platform-managed.
  • Cannot: reach any network origin it did not declare in its manifest. No undeclared server, no analytics, no third-party scripts. Code and styles always come from its own folder whatever it declares, and embedded frames and WebRTC are refused outright.

There is no per-app permission to grant or revoke. Every app in a workspace reaches the same thing, so the only access question is who can see the app, which is the lock on its row (see Apps).

How it's enforced

Four mechanisms, each independent, combine so the guarantee doesn't rest on any single check.

  1. Isolated origin. Every app runs on its own dedicated web origin, separate from the General Text shell, with its code served there over a short-lived, scope-limited token. The browser's same-origin policy keeps it walled off from your session and from other apps; your sign-in cookie is never sent to it.
  2. A Content-Security-Policy per app. connect-src 'self' plus exactly the origins the app declared, and the same list on img-src, font-src and media-src. Never on script-src or style-src: an app's executable bytes are always the ones in its folder, so a declared host can serve it data and never code. object-src 'none', form-action 'none' and base-uri 'none' close the non-fetch ways out. Declare nothing and the policy is a closed box. You can watch it in your browser's devtools: a blocked request to an undeclared origin is the boundary made visible.
  3. Two channels closed by hand. WebRTC and embedded frames can't be expressed as a host, and connect-src doesn't cover them. A shim that runs before any app code replaces the WebRTC constructors with an inert stand-in and blocks iframes (both at createElement and on insertion), each with a message in the app's console saying why. The CSP carries webrtc 'block' too, for engines that honour it, and the desktop app disables WebRTC at the engine. This is what makes "the declared hosts are the whole list" true.
  4. Brokered data, scope-checked. The app never connects to your files directly. Every read, write and live subscription is a message it passes to the shell, which checks it against the app's scope and drops anything outside. The app holds no roaming credential, so it cannot widen its own reach, and its own origin is walled off from your session, so it cannot go around the shell.

Two honest notes about where each check reaches. The app boundary is enforced on your device, by the shell that holds the key: in an encrypted workspace the server sees opaque file ids and never learns a path, so it cannot tell which app asked for what. What the server enforces independently is the person boundary: an app's files carry a per-app label, and both the sync relay and the file API refuse to deliver a locked app's files to someone outside it. That is also the shape of the lock on an app: it is a delivery filter, not a second key. Everyone in a workspace holds the one workspace key, so a lock stops a file being sent to someone from now on; it cannot make what they already have unreadable.

Synced data is end-to-end encrypted

The sandbox bounds what an app can see. A second, independent guarantee bounds what we can see: synced workspace content is end-to-end encrypted.

  • Encrypted on your device. File contents and file names are encrypted before they leave your machine (XChaCha20-Poly1305), and decrypted only on your devices and those of the people in the workspace. The server relays and stores ciphertext; it holds no key.
  • Keys stay with people, not servers. Each person has a personal keypair, protected by a passphrase and a recovery key. A workspace's content key reaches each of its people as a sealed, signed grant; granting or revoking access never routes a readable key through us.
  • You hold the recovery. Because we cannot read your data, we also cannot reset it. If you lose both your passphrase and your recovery key, your synced data is unrecoverable, by design. Keep the recovery key safe.
  • What the server still sees. Honesty matters here: the server necessarily knows your email, who is in which workspace, and coarse facts like ciphertext sizes and when syncs happen. It cannot see what any file says or what any file is called.

Encryption is per workspace: each has its own content key, held by its own people, whether it is a paid workspace or a free one.

The full picture (the key hierarchy, sharing, safety numbers, recovery, and the exact server-visible surface) is on the Encryption page.

What this means for you as a developer

You don't write any of this; it's automatic. Practically:

  • Keep your app's data under _gtApps/{your-app}/data/ (address it relative to your data folder) and it works with zero permissions.
  • To read the user's own files, use the Shared Files API (gt.grantedFiles() and gt.granted.*, see The window.gt Runtime). It is read-only: to hand a file to your app for editing, the user moves it into your app's folder.
  • Bundle everything. Third-party CDN scripts and analytics are blocked whatever you declare. Declared hosts open only data fetches (connect, images, fonts, media) to those origins. Ship a self-contained build.
  • Your app's data is plain files the user owns, readable by them, their other tools, and their AI, for as long as they keep them.

See Building Apps for the full contract.