Docs: Security

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 init and 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), or client (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 init and kept in each cube's tls/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-setup stack, every AWS call runs as yeti-provisioner, assumed for one-hour sessions. It can only create EC2 and EFS resources tagged yeti: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 as yeti-* ECS clusters and schedules, yeti/* repositories, and /yeti/ parameters and log groups.
  • Every role yeti creates lives under /yeti/ and must carry the yeti-boundary permissions 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.json to your Downloads folder. Deleting the stack revokes yeti's access.
  • Cloud cubes open only TCP 7443 and 7444, 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.

Google

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
Google 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.json before you create the stack, and look for the yeti:managed tag on anything yeti made in AWS.
  • Review yeti's access at myaccount.google.com/permissions.
  • Point an outbound firewall at the app and the cube service 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 renew issues 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.