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
| Type | Encryption | Where the key lives |
|---|---|---|
aes256 (default) | Symmetric, generated 32-byte key | FLOW_VAULT_KEY |
age | Asymmetric, recipient keys | FLOW_VAULT_IDENTITY |
keyring | Delegated to the OS keyring | OS-managed |
external | None of flow’s business | The provider authenticates |
unencrypted | Plaintext JSON | n/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_patterndescribes 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_patternseparates “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.