Security
Last updated October 3, 2026
splitscreen holds credentials that can spend your money, so the security model is the product. Here is how it actually works. The short version for a security review, with the companies that process data for us, is on the trust page.
API keys: envelope encryption
Every key is encrypted with AES-256-GCM under its own data key; that data key is wrapped by a master key held in a hardware KMS and bound to the account or workspace that owns it, so ciphertext moved to another row fails to decrypt. Recovering any key requires both our database and live KMS credentials. A database dump alone is worthless. Keys are decrypted in memory, per request, and never logged. The database table holding them accepts no client reads at all, even from you.
House keys
A reply paid with credits runs on splitscreen’s own provider accounts rather than on a key you saved. Those credentials are sealed with the same envelope encryption as your keys, under a distinct encryption context, so a house key and a member key can never be substituted for one another even by someone holding both ciphertexts. They are decrypted in server memory for the length of one request and never reach a browser.
Spending: budgets reserved before the call
Room budgets, credit balances and the caps on an owner paying for other people are reserved atomically in the database before any request starts, and the maximum length of the AI’s reply is derived from what is left. Those are not advice. They are the physical ceiling of what a request can cost. Every completed reply is committed to an append-only ledger at your provider’s own reported usage. The daily spend limit you can set on an agent token or a connected app works the other way round. It is checked before each ask against what has settled, so it stops the next ask and not one already running.
Defense in depth
We recommend keys scoped to a provider workspace with its own spend limit, such as an Anthropic Workspace limit or an OpenAI project budget. That limit is enforced by the provider that issued the key, outside our systems entirely - so unlike the caps above, it holds even if we are wrong about everything else. No provider enforces one instantly, though: a little spend can land past the line while the limit takes effect, so it bounds the damage rather than stopping it to the cent.
GitHub
A room can connect up to 5 repositories through the splitscreen GitHub App. The app asks for three repository permissions: Contents (read and write), Pull requests (read and write) and Metadata (read). It does not ask for Workflows, so it cannot change files under .github/workflows, and a proposal that touches them is refused before anything reaches GitHub. You choose which repositories the app is installed on, and the room owner then picks which of them the room uses. Each one is ticked on its own, and each one has its own branch and its own switches: nothing carries from the repository a room connected first to the one it added afterwards.
We hold no long-lived GitHub credential. We ask GitHub for a token that lasts one hour, is limited to one repository, and is kept in server memory only. A room with several repositories gets one such token each, and no token is ever good for two of them. The one exception is a read-only token that lists the repositories the app is installed on, before you pick one. If the owner ticks the box for it, the AI can also read the repositories that one pins as submodules, at the commit it pins, and only where the app is already installed on them. Each of those is read with its own token, limited to that one repository. No token is ever written to the database, a log, a reply or a browser. When the AI reads a repository it uses a token that can only read: contents and metadata, no pull request permission at all. Only the steps that push a commit, open a pull request or merge one use a token that can write.
From a repository we store the installation id, the account it is installed on, the repository’s id, name, default branch and whether it is private or archived, and each proposal the AI makes: the title, the body, the files it wrote, and the branch and pull request that came of it. We do not keep a copy of the files the AI reads.
Access control
Every table is behind row-level security: members see their own rooms and nothing else, and realtime channels verify membership at the database before admitting a listener. Budget mutations run only through database functions the client cannot call. A message pointed at somebody’s agents is checked by a trigger in the database before the row is written, so the browser’s insert and a connected agent’s go through the same rule, service_role included, and it asks membership through the same predicate the row-level policies evaluate.
Inside a room, the name and the short account id on every message the AI reads are set by the server and the text is escaped, so nobody can type something that reads to the model as a teammate’s message. Sign-in is an email address and password, a Google account, or your company’s single sign-on (SAML). You can turn on two-factor sign-in with an authenticator app in Settings, under Security; after that, signing in also asks for its six-digit code.
Abuse limits
Message sizes, key-validation attempts, and AI turns per day are all rate-limited, and every key use is recorded in an append-only audit log.
Reporting
Found something? [email protected]. We read security mail first and will credit you if you want.