So the interesting question isn't what we promise not to do — it's what the architecture makes impossible. This page is the list of "cannots", the honest list of "we don't have thats", and where to send a vulnerability.
Four decisions do most of the work here. Each one is a structural property of the code, not a policy someone could quietly relax.
~/.tryssh on your Mac. Our servers hold identity and billing rows — nothing else.
The distinction that matters: some things are private because of a promise, and some are private because there is no code path that would move them. These are the second kind.
| Thing | Reaches our servers? | Why |
|---|---|---|
| SSH private keys and passphrases | No Never | Stored in the macOS Keychain under the service ai.definable.tryssh. Nothing reads them except the local SSH engine, on the machine, at connect time. |
| Host passwords | No Never | Same store, same path. |
| Your terminal session | No Never | The PTY stream goes from your Mac to your server and into the terminal emulator. Nothing in between records or forwards it. |
| Your host list and connection settings | No Never | Rows in the local SQLite file. There is no sync service. |
| Chat history and the command audit log | No Not stored | Persisted locally. The content of a live turn does transit our LLM gateway — see below — but nothing is written down on our side. |
| Agent chat content, while a turn is running | Yes In transit | Relayed through cloud.tryssh.com to Anthropic to generate the reply. This is the honest asterisk on "local-first" — the full accounting is here. |
| Account identity | Yes Stored | Email, name and avatar from Google or GitHub SSO, via Stytch. |
| Billing state | Yes Stored | Plan, credit balance, and a ledger row per agent turn. Card numbers go to Stripe; we never receive them. |
Every SSH secret — private key material, key passphrases, host passwords — is written to the macOS Keychain under the service name ai.definable.tryssh. The local SQLite database stores the reference, never the value. There is no "encrypted secrets table", because a secrets table you encrypt yourself is a key-management problem you invented; the Keychain already solved it, and it is the thing macOS asks you about, audits, and locks with your login.
The backend that ships inside Tryssh has no database URL, no identity-provider secret, and no payment-provider key. It holds one thing: the bearer token minted for your session after you sign in, kept in the macOS Keychain. Everything privileged happens in the hosted service, behind that bearer. This is a hard rule in the codebase rather than a habit — the client is a binary anyone can unpack, so any secret in it is a public secret.
Host output passes through a redactor on its way out of the SSH channel — before it is shown to you, before it is written to the local database, and before it reaches the model. It rewrites the value side of anything shaped like a credential assignment (DB_PASSWORD=…, "api_key": "…", x-api-key: …) and a set of unmistakable vendor token shapes: AWS access key ids, GitHub and Slack tokens, Stripe secret keys, Google API keys, Anthropic and OpenAI-style keys, JWTs. Only the value is replaced; the surrounding line survives, because "connection refused for user deploy" is exactly the text you needed.
This is a mitigation, not a guarantee. A credential in a shape we don't recognise — an all-numeric PIN, a bare high-entropy string with no key name near it — will pass through. If you cat a secrets file, assume it left the machine.
The visible terminal is an SSH shell channel with a PTY, streamed byte-for-byte into a real emulator. The agent runs its commands on separate exec channels. They share an authenticated connection and nothing else — no shared buffer, no shared stream. The agent cannot read your scrollback, cannot see what you typed, and cannot type into your session. That is a property of the transport, not a rule we enforce in application code.
The gate is default-deny. A command the agent proposes runs unattended only when it matches an allowlist of read-only commands and contains no shell operators — no ;, &&, |, redirects, backticks, $(…). Everything else renders as a card with the exact command text and stops until you click.
Landing in the next release: a denylist of irreversible operations — wiping a system path, formatting a filesystem, writing to a raw block device, flushing firewall rules, stopping the SSH daemon you are connected through, dropping a database, force-pushing over a protected branch. Those will always ask, even on a host you have set to run everything automatically, and the "don't ask again" allowlist will not remember them. It is not in 0.1.6; we would rather say so than let you assume a protection you do not have yet.
Per-host autonomy is yours to raise: the default is ask, and you can move a host to run everything. That is a real setting with real consequences, and we would rather document it than pretend the product is safer than it is.
Written down so nobody has to infer it from silence.
If one of these is a blocker for your organisation, tell us which one — hello@definable.ai. It is genuinely useful for deciding what to build next.
Update promptly. Tryssh auto-updates through Sparkle over HTTPS with EdDSA-signed releases. If you disable updates, you are opting out of security fixes too — 0.1.5, for one example, shipped a broken model id that we had to patch server-side while installs caught up.
Email hello@definable.ai with Security in the subject. Include what you found, how to reproduce it, and the version you tested (Tryssh → About, or codesign -dv /Applications/Tryssh.app).
What you can expect from us:
Those are deliberately modest numbers we can actually hit, rather than impressive ones we can't. Please don't test against infrastructure you don't own, don't run automated scanners against cloud.tryssh.com, and give us a reasonable window before publishing.
The same contact details are published in machine-readable form at /.well-known/security.txt, per RFC 9116.
ai.definable.tryssh. Private keys, passphrases, and host passwords are never written to any database — ours or the local one — and are never transmitted to our servers.exec channel. The two do not share a stream, so the agent cannot read your terminal scrollback or type into it.The data-use page is the uncomfortable one. Start there.