Security
Exactly what yeti stores where, how it's protected, who it talks to, how to check it yourself, and the known limits.
Security is the product, so this page is specific: what is stored where, how it's protected, and who yeti talks to. The known limits are at the end.
The short version
- Every connection between your machines is mutual TLS 1.3, including the app talking to the cube on the same computer. There is no plaintext listener and no setting to add one.
- Model API keys, your Google client and Google tokens are sealed with AES-256-GCM on your cubes. The desktop app can save them but can't read them back.
- AWS access keys stay in your computer's keychain and never reach your cluster.
- yeti runs no server. The app has no telemetry, analytics or crash reporting.
What is stored where
| What | Where | Protection |
|---|---|---|
| Model API keys | Cluster secrets models/anthropic/api-key and models/openai/api-key, replicated to every cube |
Sealed (AES-256-GCM) with the cluster data key. Write-only from the app |
| Google OAuth client | Cluster secret google/oauth-client |
Sealed. The client ID is also stored unsealed |
| Google tokens | Cluster secrets google/<account>/token, written by a cube |
Sealed. The app never holds a token |
AWS access key (yeti-desktop user) |
OS keychain on the computer that added it | Never in the cluster, logs or UI |
| AWS SSO sign-in | OS keychain on that computer: a short-lived portal token | Never in the cluster, logs or UI |
| AWS profile | Your ~/.aws files, read by the AWS SDK |
yeti stores only the profile's name |
| Cluster CA key, the cube's own keys, this app's admin key, cluster data key | Each cube's state directory, under tls/ |
Files only the owner can read (0600 in 0700 directories). A cube refuses a key other users can read |
| Your yeti, cloud account and cube records, connection records | The cluster's replicated store, on every cube | Not encrypted at rest. Nothing secret goes in them |
The OS keychain is the macOS Keychain, Windows Credential Manager, or the Secret Service on Linux. yeti's items are under the service name dev.yeti.cloud.aws.
Between your machines
- Transport. Every connection, API and raft alike, is TLS 1.3 with certificates verified both ways. Older TLS versions are turned away.
- Identity. Each cluster has its own certificate authority, created on your computer by
cube initand name-constrained so it can't sign identities for any other cluster. Each cube and device has its own certificate. - Keys are made where they're used. A joining cube or a new device generates its private key locally and sends only a signing request. The exceptions are the cluster CA key and the cluster data key, which are sent once to each joining cube inside the verified join.
- Joining. A join code looks like
yt1.<kind>.<cluster>.<id>.<secret>.<ca-sha256>. Its secret is 256 bits, stored only as a hash, single-use by default, and expires within 24 hours (1 hour by default; codes from the app expire after 10 minutes). It's spent before anything else happens, so a failed attempt can't be retried. The newcomer pins your cluster's CA fingerprint from the code, so it checks who it's talking to before sending anything. - Roles. Every call is checked against the caller's role:
node(cube to cube),admin(your devices, including this app), orclient(tasks-only devices). Only joining and enrolling work without a certificate. - Revocation. Removing a cube or revoking a device records its certificate in the replicated state. Every cube rejects it at the handshake and on every call, which also cuts connections that are already open.
- Reach. A cube that listens on the network can be knocked on by anyone who can route to it, but without one of your cluster's certificates the only thing it answers is a join or enrollment with a valid code.
Secrets on your cubes
When you save a model key, a Google client or a Google token, the cube seals it before it's stored:
- AES-256-GCM with your cluster's 256-bit data key, created at
cube initand kept in each cube'stls/data.key. - The ciphertext is bound to the secret's name, so it can't be moved under another name.
- Only ciphertext enters the replicated log. The data key never does.
- Only code running inside a cube can decrypt a secret. Over the API, reading a secret's value is allowed for cube (
node) certificates only. Your devices, including this app, can save, list and delete secrets, never read them.
Your AWS account
- An access key is held in memory only long enough to check it with AWS, then goes into the OS keychain. It is never logged, stored in the cluster, or sent to the app's interface.
- After the one-time
yeti-setupstack, every AWS call runs asyeti-provisioner, assumed for one-hour sessions. It can only create EC2 and EFS resources taggedyeti:managed, can add that tag only when creating something (so it can't claim what you already have), and can only change or delete ones that carry it. Everywhere else it's limited to yeti's own names, such asyeti-*ECS clusters and schedules,yeti/*repositories, and/yeti/parameters and log groups. - Every role yeti creates lives under
/yeti/and must carry theyeti-boundarypermissions boundary. The setup's own role, user and policies are protected from yeti by an explicit deny. - yeti refuses your AWS account's root credentials.
- You can read the template before creating the stack: Save template writes
yeti-aws-setup.jsonto your Downloads folder. Deleting the stack revokes yeti's access. - Cloud cubes open only TCP
7443and7444, to anyone, and both speak mutual TLS 1.3 only. There are no SSH keys or passwords, EC2 instances require IMDSv2, and EC2 disks and EFS storage are encrypted. A cube's join code is kept as an encrypted (SecureString) SSM parameter in your account, and each cube's role can read only its own.
The sign-in follows the standard for desktop apps: your system browser, PKCE, and a one-time listener on 127.0.0.1 at a random port that accepts exactly one answer and checks its state value. The one-time code is handed to a cube as a sealed secret and deleted as soon as the cube has traded it for a token. Disconnect asks Google to revoke access, then deletes the sealed token.
Logs
yeti's code doesn't log secret values. As a backstop, the Logs view in the app shows any log field whose name contains token, secret, password or key as ••••. That view reads from memory: each cube keeps its most recent lines there. The service's own log files in the state directory's logs/ folder are written without that mask. Fargate cubes also log to a CloudWatch log group in your AWS account, kept for 14 days.
Who yeti talks to
| Destination | When | From |
|---|---|---|
| Your cubes | Always | The app and your cubes, mutual TLS 1.3 |
| Anthropic or OpenAI | When your yeti think | Your cubes, with your key |
| When you set up, connect or use Google | Your browser for sign-in; your cubes for tokens and Gmail or Calendar calls | |
| AWS | When you use a cloud account | The app on the computer holding the credentials. Cloud cubes fetch their join code from your account, EC2 cubes their binary from a private bucket in it, and Fargate cubes their image from its container registry |
gcr.io |
When yeti builds a Fargate cube's image | The app, to fetch Google's public distroless base image |
Nobody else. There's no yeti-run server, and the app's fonts are bundled so even the interface makes no third-party requests.
Check it yourself
- Compare the CA fingerprint on the Cubes page's Trust card with
cube cert show. - See exactly who's in:
cube members list,cube clients list,cube peers list. - Read
yeti-aws-setup.jsonbefore you create the stack, and look for theyeti:managedtag on anything yeti made in AWS. - Review yeti's access at
myaccount.google.com/permissions. - Point an outbound firewall at the app and the
cubeservice and compare with the table above.
Known limits
- Every cube holds the CA key and the cluster data key. That lets any surviving cube admit new members after a failover. It also means removing a cube revokes its certificate but can't take back either key, or the sealed secrets it already has. Rotating the CA isn't built yet, and neither is rotating the data key. If a cube may be compromised, rotate your model keys at the provider and reconnect Google.
- Keys are files. The CA key, the cube and admin keys, and the data key live in the state directory. Anything running as that user (or an administrator) on that machine can read them, and with them decrypt your secrets. Moving the CA key into the OS keychain or a TPM is a planned next step.
- Cloud storage uses AWS-managed keys. A cloud cube's disk and EFS storage are encrypted at rest, but administrators of that AWS account can read them, data key included.
- Stored documents aren't encrypted. Your yeti's personas and instructions, and your cloud account and connection records, sit unencrypted in the replicated state on every cube.
- Log files aren't masked. Only the in-app Logs view hides secret-looking fields.
- Tasks-only devices can run tasks, including tasks that use your connected Google accounts, and can read stored documents. Enroll only devices you trust.
- Certificates don't renew on their own yet. The Trust card shows when they expire;
cube cert renewissues fresh ones on new keys (restart the cube afterwards). - Forgetting an SSO account leaves its short-lived portal token in the keychain. Forgetting a key-based account deletes the key from the keychain, but the key stays valid in AWS until you deactivate it there.
- A cube that's catching up accepts connections before it knows about recent revocations, and cuts any revoked one the moment it does.
- The bootstrap fingerprint must travel over a channel you trust. For cloud cubes, yeti reads it through AWS from the machine it launched.