Vault System

The vault system provides secure storage, management, and retrieval of secrets across workspaces and executables. It extends the executable environment with multiple encryption backends.

Implementation: github.com/flowexec/vault

Provider Architecture

The vault system supports multiple storage backends through a common Provider interface:

type Provider interface {
  ID() string
  GetSecret(key string) (Secret, error)
  SetSecret(key string, value Secret) error
  DeleteSecret(key string) error
  ListSecrets() ([]string, error)
  HasSecret(key string) (bool, error)
  Metadata() Metadata
  Close() error
}

Current Providers

  • Unencrypted Provider: Simple key-value store for development and testing
  • AES Provider: Symmetric file encryption using AES-256-GCM (single key management)
  • Age Provider: Asymmetric file encryption using the Age specification (supports multiple recipients)
  • Keyring Provider: Uses system keyring (macOS Keychain, Linux Secret Service)
  • External Provider: Integration with external CLI tools (1Password, Bitwarden) via command execution

Vault Switching

Vaults can be switched using a context-based system:

flow vault switch development
flow secret set api-key "dev-value"

flow vault switch production
flow secret set api-key "prod-value"

Secret references support both current vault context (secretRef: "api-key") and explicit vault specification (secretRef: "production/api-key").

Backends

TypeEncryptionWhere the key lives
aes256 (default)Symmetric, generated 32-byte keyFLOW_VAULT_KEY
ageAsymmetric, recipient keysFLOW_VAULT_IDENTITY
keyringDelegated to the OS keyringOS-managed
externalNone of flow’s businessThe provider authenticates
unencryptedPlaintext JSONn/a

Key storage is configurable per vault, and an existing valid key in the target variable is reused rather than regenerated, which is how one key ends up shared across several vaults.

External Vaults

This is the design I am happiest with. An external vault holds links, not secrets. Each link pairs a name you choose with a reference the provider understands. Reading the name resolves the reference and reads through. Nothing is copied into flow and nothing is ever written back, so pointing a vault at a store you already use cannot damage it.

The configuration carries a get command, an optional metadata command, and two patterns that turn out to matter a lot:

  • reference_pattern describes what a reference for this provider looks like, so a typo is caught when you link it rather than weeks later when you read it.
  • not_found_pattern separates “this link is broken” from “the provider is unreachable”. Without it, an expired session is indistinguishable from a deleted secret.

Because it is read-through, flow secret set fails against an external vault and flow secret remove removes the link rather than the secret.

Injection

Secrets never appear in a command line. They are resolved at run time and handed to the process as environment variables:

params:
  - secretRef: api-key
    envKey: API_KEY
  - secretRef: production/db-password   # a different vault
    envKey: DB_PASSWORD

When something genuinely needs a file, outputFile writes one and deletes it afterwards. In container runs the same values go through a temporary --env-file, for the same reason.