Trust
How we secure your data
Forkbench is local-first by design. Your code stays on your machine. This page covers what we do hold, what we can and cannot read of it, where each defence stops, and how to tell us about a vulnerability.
What we don't claim
We don't have SOC 2, ISO 27001, or HIPAA certifications. If your buyer requires those, write to [email protected] — we can talk about timelines and what we currently do that maps to each.
What is sealed, and what is not
The short version, before the detail. Sealed means encrypted on your Mac under a key we do not have, so what reaches our servers is bytes we cannot read.
- Sealed: every note and notebook, on every plan including the free one. Every Vault secret. A Thread's objective, plan and tasks. Anything checked into a shared Thread.
- Not sealed: the text of a Talk conversation while it is live, and its title. Task and agent display labels. The name of a project folder.
- Never uploaded at all: your repositories, your terminal scrollback, your shell history, raw tool output, and the contents of any file an agent reads.
- What we can see even where we cannot read: how many items a Thread holds, how large each sealed blob is, when each changed, which account wrote it, who is on a board and at what level. Metadata is not sealed, because routing needs it.
Where a defence has an edge, we have tried to write the edge down on this page rather than let you find it later. If you spot one we have not stated, that is a bug in the page and we would like to hear about it.
What we store
- Account identity: email, optional name, OAuth provider links, hashed password (bcrypt, cost 12). Stored in PostgreSQL.
- Sessions: opaque session tokens, expiration, and the user they belong to. We don't store device fingerprints or location data.
- Subscription state: the provider customer ID, product ID, status, and renewal date. Card numbers and full billing addresses live with our payment provider, never on our servers.
- Mac app device tokens: hashed tokens that bind a paired Mac to your account, plus the device name you chose.
- Operational logs: request lines, error traces, and Sentry events. Scrubbed of email/password/token values before persistence.
What we explicitly do not store: the contents of your repositories, your shell history, or your editor state. Those never leave your Mac. Prompts and agent responses usually don't either. The one exception is Talk, covered in "What crosses the network" below.
Authentication
- OAuth providers: Google and Apple, when configured. We accept the provider's verified email and never receive your provider password.
- Email + password: passwords are hashed with bcrypt (cost factor 12). We require at least 12 characters with a letter and a number or symbol.
- Sessions: JWT-strategy session cookies, 7-day lifetime,
HttpOnly,Secure, andSameSite=Lax, bound to the canonical host. - Rate limiting: two-axis limit on the login surface — per-IP and per-email — to throttle credential stuffing.
- Soft-deleted and admin-disabled accounts: can't sign in even with the correct password.
Transport & hosting
- All traffic over TLS 1.2+. HTTPS is enforced at the proxy; plaintext requests are redirected.
- The web app, database, and background workers run on hardened, access-controlled infrastructure. Encrypted backups are taken daily and kept off-host.
- No data is shared with third parties beyond the explicit integrations listed in our privacy policy.
What agents can see
Each agent runs in a real terminal on your Mac. We open a pseudo-terminal (forkpty) and exec your actual login shell (execve) — the same shell, dotfiles, and PATH you'd get from Terminal.app. It isn't sandboxed, simulated, or proxied. That's deliberate: it's what lets tmux, vim, and your own aliases keep working exactly as they do outside Forkbench.
- We don't watch the session. We don't parse, inspect, or log what an agent types or what your shell returns.
- Repository contents, shell history, and editor state never leave your Mac. Nothing here is transmitted, and nothing is stored on our servers.
- Conversation text usually stays local too, meaning what you type to an agent and what it says back. The one exception is Talk, our web companion at forkbench.com. See "What crosses the network" below for exactly what that sends and when.
The Vault
Vault stores your API keys, tokens, and passwords in the macOS Keychain, not in our own database and not on our servers. Access is deny-by-default: your agents resolve zero secrets until you explicitly grant one. Vault is part of Pro, and it is in the 30-day trial from the day you install.
- A secret with an established destination never reaches the agent at all. The command gets a stand-in value: unguessable, minted for that one terminal and that one secret, and worth nothing to anybody else. A local proxy substitutes the real credential into the outgoing request on its way to the destination that key belongs to. The same stand-in pointed anywhere else is refused, recorded, and the request is never sent. Nothing here knows what a provider's API looks like, so it works the same whether the credential rides in a header, a query string or a JSON body.
- Who is asking is decided by the kernel, not the caller. The broker reads the calling terminal from the peer's audit token rather than from anything the caller tells it, and re-checks it before answering. A process cannot claim to be a pane it is not.
- Where it can go is not left to cooperation. The proxy that performs the substitution is made unbypassable by a sandbox profile, so a command cannot route around it. TLS is terminated only for the destinations a key in play is bound to, and only on port 443. Everything else is tunnelled unread.
- The limit, stated plainly: a secret with no established destination cannot be substituted, so it is injected into the command's environment instead, with
setenv()followed byexecvp(). It never hits stdout, never appears in the argument list (so it is invisible tops), and never enters the transcript. But a program running inside that process can still read what it was handed. For those, think seatbelt rather than guarantee. Bind a secret to its destination and the stronger path above applies. - Containing a credential is not containing authority. An agent that can legitimately call a destination can still ask it to do something destructive. That is a separate control: a destructive call stops and asks a person, in one question that names the consequence.
- A second line of defense: for a few minutes after a secret is used, we also scan your terminal's on-screen output and mask any accidental echo of the value, a stray
printenv, for instance. That's a backstop, not the primary control. - Grants are scoped and revocable. You can require Touch ID on every use, or let a grant stand while you work. Either way it's scoped to the project you granted it to, and you can pull it at any time.
- Stored sealed to the Mac holding it. Values sit in the macOS Keychain encrypted under a key that lives in that Mac's Secure Enclave and cannot be exported from it. A copied Keychain file or a stolen disk is unreadable.
- On every Mac you let in, and no further. A secret added on one Mac is usable on another a minute later. It is sealed before it leaves and stays sealed on our servers, so a new Mac has to be admitted from one that already holds the Vault: both show a short code and you pick the one that matches.
- Removing a Mac is a rotation, not a delisting. The account key is replaced and every item re-sealed, so the machine you removed is left holding ciphertext it can no longer open. It cannot be given the new key: the half of it that travels through iCloud Keychain is only half, and our half is released only to a device you have not revoked.
- A way back in when the Macs are gone. Two doors are established when you create the Vault. Your own Macs pick the key up through iCloud Keychain, and a generated recovery code works when there is no Mac left to ask. It is minted at creation because once every Mac is gone there is nothing left to mint it from, and it is generated rather than chosen so it needs no stretching to survive guessing.
- What the split costs, said plainly. Splitting the key means somebody who compromised both our servers and your Apple account could reconstruct it. That is two parties instead of one, which is the point: syncing the whole key through iCloud would have handed a stolen Mac still signed into your Apple ID every replacement key the moment a rotation published one, and quietly broken revocation altogether.
Notebooks
Notes are end-to-end encrypted on every plan, including the free one. Each field is sealed on your Mac before it is stored, so what sits on our servers is opaque bytes.
- There is no passphrase. One key per account encrypts every note, and it is never written down anywhere in the clear. Each Mac you enrol opens it with its own Secure Enclave key, which is both stronger than a memorised string and invisible in use. A generated recovery code covers the case where you lose every Mac.
- The browser never holds a key. Opening notes at forkbench.com mints a throwaway key pair in the tab and shows a short code. You approve that code on your Mac, and your Mac seals the notes key to that one tab. Nothing is written to browser storage, and closing the tab ends it.
- Encryption alone would not stop us editing. Being unable to read your notes does not, by itself, stop a server dropping one, serving an older version of one, or filing one note's contents under another note's name. Agents read notes and act on them, so each of those would be a way to influence an agent without breaking any encryption. Your Mac therefore signs a statement of what the collection contains, we store that statement without interpreting it, and a device handed a collection that does not match refuses it rather than repairing it.
Working with other people
You do not send a secret or a note to a colleague. You invite them into a Thread, and they get what that Thread holds.
- A shared Thread has a key of its own. Your account Vault opens every credential you own and your notes key opens every note you have ever written, so neither is ever handed to another person. The Thread gets its own key instead, which is the only shape where admitting somebody to one Thread admits them to one Thread.
- A role on the board is not the same as a key. Viewers and commenters work the board and hold nothing. Handing over the Thread's key happens on your Mac, as a separate decision that names the people who will be able to read it. A role written into a database row cannot produce a key on its own.
- Checking a secret in is its own decision. On a Thread nobody else is on, granting a secret just means the agents you run there may use it. The moment other people are on it, that becomes a disclosure, and nothing is published until you confirm it. An agent asking for the same thing raises the same decision, marked as an agent's.
- What is checked in stays current. Change the value in your Vault and every shared Thread carrying it is re-sealed. A snapshot would leave a team running on a credential nobody rotates.
- Three things are never copied in: a signing key, which never leaves the machine it was made on; a secret locked to a destination, because the proxy enforcing that lock lives on your Mac and not on theirs; and a credential you could mint a narrower version of, where the narrower one should be checked in instead.
- Removing somebody rotates the key, re-seals the material, and re-wraps it for everyone who remains. What they already read is theirs, and we say so rather than implying otherwise: the removal names the secrets so you can rotate them where they actually live.
- Use is visible. Every time a member's agent resolves a checked-in secret, the Thread records the secret's name, roughly who used it, and when. No value, no key. You are handing a credential to a model on a machine you do not control, and visibility is the only honest answer to that.
Cutting one terminal off
Any terminal can be sealed off from its Thread. A sealed pane is an ordinary terminal in every way except that its agent gets no board, no notebooks, no Vault and no notifications. You then hand that one pane exactly the notes and secrets it needs, and it has those and nothing else. The switch is live: flip it and a running agent loses access immediately, flip it back and it returns, with no relaunch.
The limit: for Vault access, the terminal asking is identified by the kernel and cannot be impersonated. For the board and notebooks, it is identified by an id the caller supplies. That makes sealing a scoping tool for you, rather than a jail for a program that has decided to lie about which terminal it is.
Locking a Thread to its folders
A Thread can be locked to a set of folders. Every shell in it is then started under a macOS Seatbelt profile that denies reading and writing under /Users, /Volumes and /Network apart from the folders you allowed. The profile is applied by the kernel when the process starts and is inherited by everything it spawns, so a build script, a dependency’s install hook, a Python process and an agent’s own tool calls are all bound by it whether or not they know Forkbench exists. It cannot be lifted from inside: a process under it asking for a wider profile is refused by the kernel.
File metadata stays readable everywhere, so paths still resolve and shell completion still works, but a denied folder cannot be listed or opened. System paths and Homebrew are untouched, so ordinary builds still run.
The limits: this is a lock on folders and says nothing about the network, so a locked Thread can still reach the internet. And a profile can only be applied as a shell starts, so a terminal that is already running cannot be sealed after the fact. Locking a Thread offers to restart its terminals for that reason.
How this compares to devcontainers, Docker Sandboxes and VMs, and what none of them contain.
Where a key can go
When a command uses a secret with a destination, the value is put into a child process that command runs in, and that child is started under a second Seatbelt profile denying every outbound socket except loopback. The local proxy is then the only road out, so a program that ignores the proxy settings has nowhere to dial, and the credential reaches the one destination it was bound to or it reaches nothing. This is the same shape Claude Code and Codex ship for their own sandboxes: an allow-list in front, a kernel profile behind it.
The limit, and it is the important one: this is containment and evidence, not isolation. One allowed destination is still a destination. A credential can leave inside an ordinary request aimed at the very host it belongs to, and nothing here reads what is in a payload. It closes the road around the proxy. It does not close the road through it.
The profile is applied to the single process that received a credential, never to your pane or your shell. That is deliberate: confining the pane would follow every command you typed for the rest of the session, and it would break the sandbox your own agent starts, because Seatbelt profiles do not nest. Handing a credential straight to an agent CLI is refused rather than contained, because that is not what a credential is for.
If you are here because you run with approvals turned off, the practical version is what --dangerously-skip-permissions actually skips.
What crosses the network
Nothing does, by default, beyond your account and subscription state.
- Notes sync to any signed-in account automatically, so they follow you across Macs. They cross sealed and are stored sealed.
- A board, the objective and backlog your agents work from, syncs across your own Macs, and across the people you invited on Pro. The objective, the plan and every task are sealed the same way a note is.
- Vault secrets sync sealed between your own Macs, and a secret you check into a shared Thread is sealed to that Thread's key. We hold ciphertext in both cases.
- Talk works differently from the three above. Once you're signed in on a paid plan it runs automatically; there's no separate switch. It can only reach a conversation that's already live on your Mac right now (it can't start work on a machine that isn't in front of you), and what it sends is the parsed text of that conversation, your prompts and the agent's replies, so you can watch or continue it from a browser. Raw tool output and terminal scrollback are stripped out before anything is sent. If the conversation isn't part of a shared board, our copy of it is deleted about three minutes after it stops being live; if it is, it's kept until you delete it. Sign a Mac out from your account's device list to end its Talk link immediately.
- Talk conversations are the one thing on this page that is not end-to-end encrypted, and we would rather say so here than have you assume otherwise. Your notes, your secrets and your board are sealed under keys we do not hold. A conversation relayed to the web is not: we hold the parsed text while it is live, because rendering it in a browser and routing your reply back to the right terminal is what the feature does. It is also the most valuable thing left, since a transcript records what an agent read and did. Sealing it is the next piece of work on this list, and this paragraph changes when it lands.
None of this runs your agent anywhere but the machine in front of you. There's no remote execution: syncing a board, a note, or a secret moves data between your own devices, or to a collaborator you invited. It never means an agent runs somewhere else.
What agents can't do alone
Forkbench assumes agents will sometimes be wrong, so a few things are structurally reserved for a person. The gate sits at the end of the work, not the start: an agent can open a task and get moving on it right away, but it can't be the one who judges whether the result is good.
- An agent can never verify its own work. When a task goes up for review, the check runs against who actually did or requested it — the agent that wrote or worked a task can't also be the one who signs off on it. That runs against the real author every time, not as a UI convention.
- Reviewer authority is human-anchored. Every agent starts out as a contributor, and a contributor can't verify anything. The only thing that turns one into a reviewer is a person acting in the app's Thread overview — no tool call reaches that switch, so an agent can't mint itself a reviewer, not even by registering under a fresh identity.
- Finishing a task files a report, not a verdict. Completing a task moves it to "awaiting acceptance"; a person has to accept it before it counts as done. An agent's own "I'm finished" is a claim for someone to review, not a decision it gets to make.
- Deleting a task is still a proposal. An agent can ask for one to be removed. Only a person can actually delete it.
- The git panel has no merge command, and it isn't something an agent can drive in the first place: there's no tool that reaches it. Every stage, commit, and push you see there is something you clicked yourself.
- A scheduled note that's gone stale holds and asks instead of firing blind. If your Mac was asleep past the moment a queued instruction was due, we wait for you to say "run it" rather than sending a stale prompt into a session that's moved on.
Agent hooks
If you use Claude Code, Codex, or Gemini CLI and turn on Forkbench's Live agent prompts or Wake fast path settings, we write a small hook into your agent's own settings file (~/.claude/settings.json for Claude Code, the equivalent file for the others). Both settings are off by default.
- Live agent prompts adds a
PreToolUsehook scoped to only two tools (asking you a question, or proposing a plan), so our UI can show that prompt the instant it happens instead of you switching to the terminal. - Wake fast path adds a
Stophook that runs when an agent's turn ends. It asks Forkbench's local board whether anything is waiting for this session, a teammate's update, a comment, a reply, and if so, tells the agent to keep going instead of ending its turn idle. That's what wakes a session when work lands on your backlog. - We merge in, we don't overwrite. We edit only our own marked entry in your settings file and leave every other hook you have untouched. Turning the setting off, or deleting the entry yourself, is safe: the fast path is a convenience, and losing it just means we fall back to our normal way of noticing your agent is idle.
- We only ever write to your user-level settings (
~/.claude/settings.json), never a project's local.claude/settings.json. That file is gated behind Claude's own workspace-trust dialog, so an auto-written hook there wouldn't fire without your approval anyway.
Vulnerability disclosure
We take security reports seriously and acknowledge every report within one business day.
- Email [email protected] with subject prefix
Security disclosure:. - Include reproduction steps, affected URL or version, and any proof-of-concept code. Please give us reasonable time to fix before publishing.
- We don't operate a paid bug bounty yet. We do credit reporters in the changelog by request.
- Safe harbor: good-faith testing that doesn't degrade service or access customer data will not be pursued legally.
Privacy details
For the legal version — what data we collect, how long we retain it, your rights, and how to delete your account — see our privacy policy.