Aileron Docs

The Vault

The vault is where Aileron stores your credentials — OAuth tokens for Slack, API keys for Linear, refresh tokens for Gmail, and so on. It lives in your home directory, encrypted at rest with a passphrase you control. The runtime can only reach the credentials inside it while a process you started is running; nothing on disk is recoverable without your passphrase.

If actions are what the agent can do and connectors are how it gets done, the vault is what makes that safe. Without it, every credential the agent might use would have to live somewhere — in environment variables, in a config file, in the agent host’s process memory. The vault is the answer to “where do these credentials actually go.”

The shape of it

The vault is a single encrypted file at ~/.aileron/secrets.json. Each entry is a credential bound to a name you chose:

oauth2/slack/work          ← OAuth2 token for your work Slack
oauth2/slack/personal      ← OAuth2 token for your personal Slack
api_key/linear/team        ← API key for your team's Linear
oauth2/gmail/work          ← OAuth2 refresh token for work Gmail

These names — binding identities — are how actions and connectors reach into the vault. An action that posts to Slack doesn’t say “use this specific token”; it says “I need an OAuth2 credential with the chat:write scope,” and the vault resolves the request to a concrete binding the user picked.

You see the binding identities in aileron binding list. You set them up with aileron binding setup (proactively) or at first use (when an action invokes a connector that needs a credential you haven’t bound yet). The full lifecycle is covered in ADR-0006: Capability Binding UX.

The passphrase

The vault is encrypted with a key derived from a passphrase you chose. The first time you run Aileron, it asks you to set one:

$ aileron launch
No vault detected. Create one now? [Y/n] Y
Choose a passphrase to protect your credentials:
> ********
Confirm passphrase:
> ********

Deriving encryption key (Argon2id, 64 MiB memory, ~500ms)...  ✓
✓ Vault created at ~/.aileron/secrets.json

Every time you start Aileron after that, you re-enter the passphrase to unlock the vault:

$ aileron launch
Vault is encrypted. Enter passphrase to unlock:
> ********

Verifying passphrase...  ✓
✓ Vault unlocked
✓ Listening on http://localhost:8721/v1

The passphrase never goes to disk. The runtime derives an encryption key from it (using Argon2id, the modern password-hashing standard), holds the key in memory, and uses it to decrypt credentials when the agent needs them. When the runtime exits, the key goes with it. Next time you start, you re-derive.

What the vault protects you from

The threat model the vault is designed for:

  • Disk theft. Your laptop is stolen. The attacker has your ~/.aileron/secrets.json file but not your passphrase. Without the passphrase, the file is opaque. Brute-forcing the passphrase against Argon2id is intentionally slow.
  • Backup leaks. Your ~/.aileron/ directory is in a cloud backup. The same property holds: the file is encrypted; the backup provider sees ciphertext.
  • Process inspection by other processes. A second process running as your user can read your home directory, but cannot reach the in-memory key inside the running Aileron process.
  • Connector compromise. Even a malicious connector binary cannot reach the vault. The runtime mediates every credential use; connectors receive opaque capability handles, not credential bytes (see Connectors and ADR-0005).

What the vault is honest about

Some things the vault does not protect you from:

  • A compromised running process. While Aileron is running with the vault unlocked, the encryption key is in process memory. A privileged attacker who can read Aileron’s memory can extract it. (The post-MVP TEE-backed variant addresses this; v1 doesn’t.)
  • A weak passphrase. Argon2id makes brute-forcing slow but not impossible. A short or guessable passphrase undermines the protection.
  • Phishing or social engineering. If you’re tricked into entering your passphrase into something that isn’t Aileron, the vault doesn’t help.

The right framing is: the vault is strong protection at rest and a meaningful boundary while running, but it’s not magic. Choose a strong passphrase.

How a credential travels

This is the path a credential takes when an action invokes a connector that needs it:

  1. Action invocation. The agent calls an action, say ship-update. The action declares it needs oauth2(scope=chat:write).
  2. Binding resolution. The runtime looks up the user’s binding for that capability — say, oauth2/slack/work.
  3. Credential decryption. The runtime decrypts the credential value from the vault using the in-memory key.
  4. Capability handle. The runtime issues an opaque handle to the connector (not the credential itself).
  5. Connector emits a request. The connector formats an HTTP request with placeholder authorization, including the handle.
  6. Runtime intercepts and signs. The runtime sees the outgoing request, resolves the handle to the actual credential, attaches the auth header, and forwards the request.
  7. Response returns. The connector sees the API response; never sees the credential bytes.
  8. Connector terminates. The instance exits; any handles it held become invalid immediately.

The connector’s only reach into the credential world is through this mediated path. There is no vault.read("...") API, no host function that exposes credential material, no way for the connector to ask for raw bytes. By design.

What’s encrypted, what’s not

Inside the vault file, only the credential values are encrypted. The metadata that helps you reason about your bindings — the kind, the scope, the identity — is plaintext:

{
  "oauth2/slack/work": {
    "metadata": {
      "kind": "oauth2",
      "scope": "chat:write,channels:read",
      "encrypted": "true"
    },
    "ciphertext": "<encrypted credential value>"
  }
}

This is intentional. aileron binding list should work without prompting for the passphrase — listing the names of your bindings doesn’t require decryption. Only when an action actually needs to use a credential does the runtime decrypt.

When you forget the passphrase

There is no recovery in v1. A forgotten passphrase means the vault contents are unrecoverable. You’d need to:

  1. Rotate every credential at the upstream service (revoke OAuth tokens at Slack admin, reissue API keys at Linear, etc.).
  2. Delete ~/.aileron/secrets.json.
  3. Re-create the vault with a new passphrase.
  4. Re-bind every credential through aileron binding setup or first-use binding.

This is the honest trade-off for zero-knowledge encryption. Adding a recovery mechanism — escrow, recovery codes, anything — would mean something in the system holds enough information to recover your credentials, which is exactly what we’re trying to prevent. Recovery codes might land post-MVP; v1 ships without.

Beyond v1: where the vault is going

The Stage 1 design (a local encrypted file with passphrase-derived key) is what v1 ships. The same on-disk format and cryptographic primitives extend naturally to two future variants, both of which are already implemented in the codebase but not yet wired into v1:

  • Stage 2 — TEE-backed. When Aileron Cloud (the hosted backend) ships, credentials decrypt only inside a Trusted Execution Environment (Google Confidential Space). Even Aileron operators cannot inspect credential memory. This enables async / scheduled actions where the runtime needs credential access while you’re offline.
  • Stage 3 — Browser enclave. The KEK never leaves your browser. Aileron sends encrypted credential requests; the browser decrypts and forwards them. True zero-knowledge for hosted Aileron — even Aileron’s infrastructure cannot access plaintext.

Both are post-MVP and are pending security review. They’re forward-compatible with the v1 file format: the same vault file gets a stronger custodian without a migration.