harnsy: security brief
For the people who decide whether harnsy may run on a company machine. These are facts from the developer’s own reading of the harnsy 0.9.0 code (source revision e7a7798d, the 0.9.0 release); it is not an independent audit or a certification, and no running installation was tested. Later versions can differ: the release notes say what changed. Where a line says “by default”, a setting changes it; where it says “no” or “never”, the review found nothing in the code that does it. Developer: Pavel Buchnev, individual entrepreneur (Georgia), registration number 345621981. Questions: hello@harnsy.dev.
1. What harnsy is
harnsy is a local program that connects the command-line tools a person already uses (Claude Code, Codex, OpenCode) so that their agents can message each other and work as one team. Commands that an agent runs go through one of those tools, under that tool’s own permissions, which harnsy does not override. harnsy itself starts those tools, runs git for local checks and, after a person enables each one, scripts called actions (section 9). One person’s workstation is the design target (see section 13).
2. What runs and what is installed
- One local process,
harnsy serve(the node), under the person’s own user account. No root, no sudo. - Linux: a systemd user service. macOS: a LaunchAgent. Windows: a Task Scheduler task at logon, least privilege.
- The binary goes to
~/.local/bin/harnsy(Windows:%LOCALAPPDATA%\harnsy\bin). - An optional tray app only reads the local dashboard address and opens no port.
- On Windows, harnsy opens agents in its own terminal host, which listens on a socket or pipe for this user only, not on TCP.
harnsy uninstallremoves the service, the tray app and the binary and reverts the installer’s own settings edits. It keeps the data folder (section 8).
3. Changes to other tools’ settings
The installer changes the settings of the tools it connects only after showing the change as a diff and getting an explicit yes; it backs the file up first. Each is optional.
- Claude Code (
~/.claude/settings.json): a session-start hook (a briefing script that reads only the local address), a status-line wrapper, a permission-request hook to the local address, and, if you agree, a rule set that allows harnsy’s own tools. - Codex (
~/.codex/hooks.json): the same session-start hook. Itsconfig.tomlis not changed. - OpenCode: no settings file is written.
- No MCP server is registered by the installer; it only prints the command.
- Changes made later from the dashboard (a permission rule added to a Claude settings file, trust of a project folder) are not reverted by uninstall.
4. Ports and who can connect
| Port | Default | What it serves | Who can reach it |
|---|---|---|---|
| 7788 | 127.0.0.1 only; the node refuses to start on a non-loopback address | dashboard, full API | any process on this machine |
| 7790 | 127.0.0.1, on (can be turned off) | API for agents without the admin routes, a proxy for the Assistant’s model requests, the key proxy | any process on this machine |
| 7789 | off until a harnsy Max license allows nodes and a node token is issued | other machines of the person’s cluster; each request needs a token and a scope | holders of a node token |
| access from outside | off | the dashboard from a phone or another network | devices signed in with a QR code and a passkey; without a session it serves only the sign-in pages, public file links and the app’s static files |
- The local ports check that the request names the loopback host, its origin and its type, which blocks a web page in a browser from driving them.
- There is no separation between users on the loopback address: any local account that can open a connection to it gets the API without a token. harnsy is built for one person’s workstation; on a shared multi-user host every local account can reach it.
- Using a cluster port on a network address, or turning on outside access, is the person’s explicit choice. Both use TLS.
5. Outbound connections
Without a license key, harnsy by default opens no connection off the machine. Every connection below happens only after a person adds a key, adds a connection or starts a feature. Some need no key: dictation, the Assistant’s model requests, and a machine that has joined a main node (it can use the hosted relay on the main node’s license).
| Destination | When | What is sent | Needs | How to turn it off |
|---|---|---|---|---|
License service (lic.harnsy.dev) | after a license key is added: activation, then a check about once a day; on removal | the whole key; a hash of the machine id (the raw id never leaves the machine); a random one-time value; a label (by default the OS and four characters of the hash, not the host name; a label you type is sent as typed); OS and architecture; harnsy version and build date; a public key when the key lends seats | a license key | remove the key |
| License service, short-key exchange | when a person enters a short key HRSY-… | the code, machine hash, label, OS and architecture, version | paste the license file instead | |
| License service, relay ticket and name service | hosted relay for nodes, or hosted outside access | the key, machine hash, a relay-key fingerprint; a signed DNS challenge value | harnsy Max | turn the relay or outside access off |
Hosted relay (relay.harnsy.app, port 443) | nodes connected over the relay, or hosted outside access | encrypted streams only (section 10); the relay sees the network address of each connection, which node and license (as hashes in the log), the time and the amount of data, not the content | harnsy Max | harnsy cluster relay off; outside access off |
| Let’s Encrypt | hosted outside access, or an own domain with automatic certificates | an account key, an order for the node’s name, a certificate request | none: in hosted mode it is asked before the license is checked | outside access off; use your own certificate files |
crt.sh | hosted outside access, every 6 hours | the node’s public name (Certificate Transparency watch) | none: in hosted mode it is asked before the license is checked | outside access off |
| The person’s own main node | a machine joined to a cluster | the node token, node name, build, license proof; then the traffic of the cluster (messages, agent list, files, terminal streams the person allowed) | a license or a borrowed seat | leave the cluster |
| Telegram, Discord | a bot is added | the bot token; texts and .md files agents post to the person’s chats; voice notes fetched for dictation | connections | archive the bot |
The browser’s push service (fcm.googleapis.com, web.push.apple.com, updates.push.services.mozilla.com, *.notify.windows.com; the address comes from the person’s browser, only these hosts, https on port 443) | an agent waits with a question or a permission request (a wait for input only if the person ticks it), on a device where push is on, outside that device’s quiet hours; also when the person presses “test” | an encrypted message (RFC 8291) that only your browser can read: the kind, the agent’s name, the project and team name, the wait’s internal number, the language; no question text, command, file path or token. The push service sees the endpoint, the network address of the node that sends, the time and the size of each push, not the content; the phone shows the agent’s and the project’s or team’s name, not the question | harnsy Max (mobile); a device that is still signed in | Settings › Notifications: turn it off or remove the device; signing out or revoking a device stops its pushes |
api.openai.com (or an address the person sets) | dictation: the dashboard microphone, voice notes, “test” | the audio (kept in memory only), model, language, the person’s key | none | don’t add a dictation connection |
api.anthropic.com, api.openai.com, api.z.ai, open.bigmodel.cn | only when a person presses “test” on a connection | a Claude token: one real one-token request; API keys: a list-models request | connections | don’t press “test” |
auth.openai.com | a person starts “ChatGPT login” for a Codex connection | the device-code login | connections | don’t use it |
api.github.com (or a GitHub Enterprise address) | a GitHub connection is added or tested | the token; read-only requests | connections | don’t add it |
| Hosts of keys in the key store | an agent calls the key proxy | the agent’s request with the key put in by harnsy; private, loopback and cloud-metadata addresses are refused | key store | delete the key |
api.anthropic.com, chatgpt.com, api.openai.com through the local model proxy | only for the Assistant’s sessions: harnsy forwards the tool’s own request unchanged | the tool’s request with its own key header; nothing is stored | don’t run the Assistant | |
| An own OpenCode server | a person adds one | the model list, proxied requests | connections | don’t add it |
| An S3 endpoint the person sets | storage backend set to S3 | attachments | harnsy Max | keep the default backend, the local disk |
- What the license service keeps with the record of your key, for each machine the key is activated on: the machine hash, the label, the OS and architecture, the harnsy version, the first and last time it was seen, the end of its offline period, and when and by whom it was released; also a log of activations and releases and the days on which the machine checked. It does not keep the build date, and it writes no IP address of a program request to its database. Its web server logs the IP address, time and address of each request for 7 days, with secrets masked. The same is said in the privacy notice for key applications.
- Telemetry, analytics and crash reports: none; the program contains no such code. The license check above does tell the license service that a machine with this version is in use.
- Updates: the program never checks for or installs updates. Only the install scripts ask GitHub for releases, and only when a person runs them.
- harnsy does not call AI model APIs on its own. The only calls are the “test” button, dictation and the forwarding for the Assistant described above. Usage and limits are read from local files.
- Most clients honor the
HTTPS_PROXYsetting; the model proxy and the key proxy deliberately ignore it. - harnsy’s own
gitcalls cannot reach the network.
6. What harnsy does not do
- No telemetry, analytics or crash reports.
- It never checks for or installs updates by itself.
- It does not read Claude Code’s login tokens (
~/.claude/.credentials.jsonor the system keychain). - It never reads OpenCode’s
auth.json. - The raw machine id never leaves the machine; only a hash does.
- An agent’s request never makes harnsy run a command in a terminal.
- No secret is put on a command line: keys reach a tool through its environment or a file.
- Agents cannot answer a permission prompt through harnsy’s agent API. This does not protect against another process of the same user.
7. Credentials and keys
- Claude Code: harnsy reads from
~/.claude.jsononly account fields (an account id, kept as a hash; the email; the plan; the organization name) for usage figures. - Codex: the sign-in file is read into memory; only the email, the plan, a hash of the account id and the token expiry are kept. The tokens of the person’s own Codex login are not stored or sent.
- Keys the person adds (subscription tokens, API keys, bot tokens, a GitHub token, key store values, an S3 secret) are sealed in the database with AES-256-GCM. The sealing key is a separate file,
secret.key, readable only by the user. - A tool gets such a key through its environment, never on the command line. One exception: a Codex connection writes its decrypted sign-in file to the data folder, readable only by the user, because Codex reads only a file.
- The key store: the agent never sees the value. harnsy puts it into the outgoing request and removes it from the answer.
- The license key is a plain file in the data folder, readable only by the user.
- Invites, seat tokens, outside-access devices, pairing codes and file links are kept only as SHA-256 hashes; passkeys as public keys only.
- Cluster join tokens are stored as plain text in the database and in the member’s cluster file (readable only by the user).
8. Data on disk
- Everything is under
~/.harnsy(or the folder inHARNSY_HOME), created readable only by the user (on Windows with a user-only access list when harnsy creates the folder). harnsy.db(SQLite): messages between agents in full, events, work items, teams and roles, usage figures, the log of terminal actions, connections and the key store (sealed), logs of key and outside-access use.- Phone notifications: for each device
harnsy.dbkeeps the browser’s address (endpoint) and keys, a short browser and system label, the language and the settings; what was sent is kept 7 days; the node’s push signing key is a file readable only by the user. When you remove a device or turn push off, its pushes stop, but its address and keys stay in the database, marked as removed. - Files and attachments are stored as they are, up to 100 MB per file and 5 GB in total. Files dropped onto a terminal are kept 7 days.
- Terminal screens are not stored. They live in memory; the log records the action (who, which pane, the result), not the text on the screen.
- Only the sealed secrets are encrypted at rest. Everything else is plain and protected by file permissions.
- Retention: messages, events, work items and usage history stay until the person deletes them. 30 days: the terminal action log, sent and failed chat messages, the agents’ action log (adjustable). 90 days: the key store log and the outside-access log. 7 days: the file trash and terminal uploads.
- To delete everything: run
harnsy uninstall, then remove~/.harnsyby hand (also~/.local/share/harnsy/backups, and on macOS~/Library/Logs/harnsy). There is no single command that wipes the data.
9. What agents can do through harnsy
- Agents use the local API and MCP to send messages, see other agents, run teams and work items, open new agents and read and write the node’s shared files (only
exchange/anddocs/). - A message reaches a tool as a normal user turn through that tool’s own channel, not as keystrokes.
- Agents are not given: cluster and remote-member control, license changes, the admin of connections and the key store, outside access, terminal attach, typing and upload, or the answers to permission prompts.
- Terminals: an agent never runs a command in a pane through harnsy. Typing, attaching, uploading and opening a shell are for the person only. Reading a pane’s screen is allowed for the agent that opened it and its lead. This applies to panes harnsy opened.
- Permission prompts of Claude Code are shown on the dashboard; the tool’s own terminal dialog stays live and the first answer wins. From a chat (Telegram) answering is off by default, limited to the owner’s private chat, and only “once” or “reject”.
- What harnsy itself executes: the command-line tools in terminal panes,
git(local only),codex queue, and “actions”, scripts that run only after the person enables each one (pinned by a SHA-256) and that an agent may start only if the person allows it. - Codex’s project trust is passed on by default; Claude Code’s folder trust is off by default.
10. Between machines, relay and outside access
- Several machines (harnsy Max): the link between nodes uses TLS by default when the cluster port is on a network address. The main node makes its own key and a joining machine pins its fingerprint, received inside the join token, so nothing is trusted on first use. A TLS-only port answers cleartext with an error. Tokens can be revoked.
- The installed service keeps the default cluster address
127.0.0.1:7789as plain HTTP on the machine itself, for a TLS proxy in front. - Hosted relay for nodes (beta): two TLS layers; the inner one is the main node’s pinned TLS end to end, so the relay cannot read tokens or messages.
- Hosted outside access: the relay reads only the TLS server name and passes the encrypted bytes; TLS ends on the person’s machine. Caveat: whoever controls DNS for the node’s hosted name (a name under
n.harnsy.app) could obtain a certificate for it. In the hosted mode that is us: we run that DNS zone and the relay. harnsy watches Certificate Transparency every 6 hours and alerts on a certificate it did not request. That detects such a certificate; it does not prevent it. - Sign-in from outside: a QR code made on the local dashboard (valid 5 minutes, once), then a passkey; every write needs a passkey check under 15 minutes old; a device is signed out after 7 days idle (30 days at most); the cookie is
SecureandHttpOnly. Full control is off by default and ends by itself after 2 hours. - The hosted relay runs at Hetzner Online GmbH in Falkenstein, Germany. Its log keeps for 14 days: for each connection of a node, the time, a hash of the node id, a hash of the license id and the source address cut to /24; for each relayed stream, the time, the hash of the node id, the source address cut to /24, the bytes in and out, the duration and how it ended. It holds no content, URLs, names or emails. While a node is connected, the relay holds its license id in memory to apply limits and bans. A failed TLS handshake is logged with the full source address and port, also for 14 days. The server’s own administration logs (SSH logins and blocked packets, with full addresses) are kept for 30 days. For a firewall: the relay is
relay.harnsy.appon port 443; the license service islic.harnsy.dev.
11. Updates and build integrity
- Updates are manual: the person runs the installer again or replaces the binary.
- Each release publishes archives per system and a
SHA256SUMSfile on GitHub; the installer checks the archive against it. - The
harnsybinary carries an Ed25519 signature and checks itself at start. A failed check does not stop it: it runs as the free edition and the dashboard’s license answer shows an integrity note.SHA256SUMSand the tray binary are not signed. - Builds use
-trimpathand no C code (CGO_ENABLED=0). They are not claimed to be reproducible. - A reviewer can run:
sha256sum -c SHA256SUMS --ignore-missing;go version -m harnsy(Go version, tags, revision); after the startcurl -s http://127.0.0.1:7788/api/licence, where the integrity field is empty and a build date is set. - Offline: a license key is checked on the machine; harnsy Max keeps working offline until the date the license service gave at the last check: 30 days after it, or one year after it for a paid license whose term has ended (a gift never gets the year).
12. Source and license
The source is closed (“All rights reserved”); the builds are published on GitHub (harnesy/app). The license terms are in LICENSE and the EULA.
13. Limits to know before you decide
- A shared multi-user host: every local account reaches the local API (section 4).
- Cluster join tokens are plain text on disk (section 7).
- Only the sealed secrets are encrypted at rest; everything else relies on file permissions.
- The hosted outside-access certificate caveat (section 10).
- Hosted outside access contacts Let’s Encrypt and crt.sh even without a license key.
- On Windows, some key files rely on the access list of the data folder, which harnsy sets only when it creates that folder.
- The setting
strict_localdoes not refuse a local caller that sends no token. - A phone device that was removed keeps its address and keys in the database, marked as removed (section 8).
SHA256SUMSand the tray binary are not signed; builds are not reproducible.- A modified binary is not refused: it runs as the free edition.
harnsy uninstallleaves the data folder and later dashboard edits to Claude settings.- harnsy is not a sandbox: what an agent may do is decided by the tool it runs in.
Report a vulnerability: hello@harnsy.dev.
harnsy is not affiliated with Anthropic, OpenAI or OpenCode; their names are theirs.
Contact: hello@harnsy.dev