How Reemoat is secured, and what it does not protect
There is no sandbox: an agent runs as your own OS user, with your files, your credentials and your network. What Reemoat protects is the path between your devices and that machine, which is end-to-end encrypted, so the relay carries bytes it holds no key for.
Who can read what
| Who | What they can read or do | What limits it |
|---|---|---|
| The relay | Authorizes each connection, then moves bytes. Prompts, diffs, file contents and terminal output are ciphertext to it. | It holds no key. |
| The operator of a control plane | Holds accounts, machines, grants and the key that signs every token. Can talk to your daemon as you. | Nothing in the software. Running your own removes this party. |
| The model vendor | Whatever the agent sends it under your account: your prompts and the code it reads. | Not Reemoat. The encrypted channel ends at the daemon; from there the agent talks to its vendor as it does from your terminal. |
| Someone holding a grant on a machine | Can drive that machine through the relay. An agent there runs as the daemon's OS user. | Revoking the grant stops new requests at the relay at once. |
| Someone with your unlocked device | The signed-in app acts as you: a device authenticates as a person. | You retire the device. A token it already holds lasts up to 360 seconds. |
The channel
The app and the daemon run Noise_. There is no
plaintext mode: a daemon that cannot speak the encrypted one is reported
unreachable. The relay forwards ciphertext, and the daemon dials out to it, so
no port is opened.
The control plane signs a token for one machine. It lives 300 seconds, with 60 seconds of clock leeway either side, and names the requesting device's public key. The daemon refuses one whose key is not the key the handshake authenticated, so a token copied out of a log cannot be spent from another device.
Three limits. Tokens are not replay-tracked. The token travels as a query parameter on the app's WebSocket handshake to the relay. A daemon on the app's own computer is reached over loopback, with no channel to bind to, so there a token is a bearer token for its remaining life.
Keys
The app's static key is a device key, kept in the operating system's keyring, or in a file only your user can read where a keyring will not hold it. The daemon's is a machine key it generates on first start; the control plane reports it to the app.
Why the client is a native app
The app is a native binary "with the whole interface compiled into it, so the server it talks to serves it no JavaScript and cannot replace any." The README adds:
It keeps the session in the operating system's credential store and holds the device key that opens an encrypted channel to each machine — the one thing a browser tab could not do, and the reason there is no browser version.
So a control plane cannot replace the app's interface. That does not make its operator untrusted.
Trusting the operator of a control plane
SECURITY.md says:
an operator is still trusted completely, and self-hosting is the only version of "not trusted" this system has.
The control plane's database holds the Ed25519 private key that signs every token. Encryption takes the relay out of the payload path, not the operator: they can issue a token naming a device key of their own and talk to your daemon as you.
- On a control plane you do not run
An admin can revoke your machine, register a new one under the freed name and enroll it on their own hardware. The machine row names whose enrollment code a machine enrolled with when it was not yours; if you do not recognise the name, re-enroll the machine.
The control plane keeps your login name, a password hash, an optional email address, the IP address and
User-Agentof each sign-in, and a name and platform per device. There is no access log: nothing records who reached which machine when. - If you run your own
The database is the whole fleet: a leak of it, or of a backup, is a total compromise. The first admin's password and API key are printed to the container log; rotate them after the first sign-in.
What is accepted and not protected
- No sandbox
The agent is a child of the daemon, with your uid, home directory,
~/.ssh, browser profile and other repositories. Its working directory is not confined, and it pushes with your git credentials. - Git hooks run
Creating a session's worktree runs the repository's own
post-checkouthook and LFS filters. A hostile repository is as dangerous here as in your own terminal, and no more. - Agent settings can answer before you do
Where
~/.claude/settings.jsonblanket-allowsBash,EditorWrite, Claude Code decides for itself and Reemoat never sees a request: an allow rule means "without manual approval". Cursor writes an "Allow always" to its allowlist in~/.cursor/, for your terminal and the app alike.cli-config.json - Agents can message each other
On by default since 0.12.0: an agent can hand work to any other session on that machine or on your other machines, and the daemon limits how much, never what. Settings → Permissions switches it off for every machine you own.
- Plugins run as you
A plugin is somebody else's code, with your files and keys. Its declared scopes are "hygiene rather than a fence".
- The installer is
curl | shNothing pins the script's content: there is no signature and no checksum.
- Unsigned builds
The macOS and Windows builds are not signed yet, so the system warns the first time. Install has the steps.
- Revocation has windows
An open WebSocket outlives a revoked grant or a retired device until its token dies, plus one 20-second ping tick. Over loopback, both keep working for up to 360 seconds. An API key is in no device list, and only its holder can retire it.
- Nothing notifies you
There is no push, no email and no bot. A permission request waits until you open the app.
Reporting a vulnerability
Report it privately, through a GitHub security advisory. There is deliberately no security email address.
There is no bounty and no response-time commitment, because the project is one
person. Only the latest release and main are supported.
When this is the wrong tool
- You want the agent kept away from the rest of the machine.
- You cannot trust the operator of a control plane and will not run your own.
- Several people share one deployment and do not trust each other. SECURITY.md assumes "One person, one machine, many agents, and the machine is yours."
- You need a record of who reached which machine, revocation that cuts every path at once, or an alert when an agent asks for permission.
- You need signed builds, a bounty or a response-time commitment.