External Secrets
Use 1Password, HashiCorp Vault, AWS, GCP, or Azure Key Vault as your secret store instead of the OS keychain.
By default, Earl stores secrets in the OS keychain. For team deployments where secrets live in a centralized store, you can point Earl at an external provider instead. The template syntax doesn't change — you just swap the key name for a URI.
URI syntax
Each provider has its own URI scheme:
| Provider | URI prefix | Example |
|---|---|---|
| 1Password | op:// | op://Personal/GitHub/token |
| HashiCorp Vault | vault:// | vault://vault.example.com:8200/secret/data/myapp#api_key |
| AWS Secrets Manager | aws:// | aws://us-east-1/myapp/api_key#value |
| GCP Secret Manager | gcp:// | gcp://my-project/my-secret#latest |
| Azure Key Vault | azure:// | azure://my-vault.vault.azure.net/my-secret#version |
The fragment (#field) identifies which field or version to extract from the secret. For AWS, GCP, and Azure this is the key within the secret JSON object. For 1Password it's the field name. For Vault it's the key within data.
Using URIs in templates
Templates reference external secrets the same way they reference OS keychain secrets — in annotations.secrets and auth.secret. Use the full URI as the key name:
annotations {
secrets = ["op://Personal/GitHub/token"]
}
operation {
protocol = "http"
method = "GET"
url = "https://api.github.com/user/repos"
auth {
kind = "bearer"
secret = "op://Personal/GitHub/token"
}
}The URI in auth.secret must match one of the entries in annotations.secrets. Earl validates this when the template loads.
Mixing sources
OS keychain secrets and external secrets can coexist in the same template. Any key name that looks like a URI (op://, vault://, etc.) goes to the corresponding provider. Bare key names go to the OS keychain.
annotations {
secrets = ["github.token", "op://Team/Stripe/api_key"]
}Error behavior
If an external provider is configured and the URI resolves, that value is used. If the provider fails — timeout, authentication error, secret not found — Earl exits with an error. It does not fall back to the OS keychain for URIs that match a provider scheme.
Provider setup
1Password
1Password uses the op CLI. Install it and authenticate before using op:// URIs:
# Install from https://developer.1password.com/docs/cli/
brew install 1password-cli
# Sign in
op signinNo config.toml entry is required. Earl calls op directly and passes it the URI.
[secrets.providers.onepassword]
# No additional configuration needed.
# Earl uses the authenticated op CLI session.HashiCorp Vault
[secrets.providers.vault]
address = "https://vault.example.com:8200"
auth_method = "token" # token, approle, or kubernetesAuthentication is handled through environment variables depending on the method:
Token auth — set VAULT_TOKEN.
AppRole auth — set VAULT_ROLE_ID and VAULT_SECRET_ID.
Kubernetes auth — set VAULT_ROLE. Earl reads the service account token from the default path (/var/run/secrets/kubernetes.io/serviceaccount/token).
The address field is required. It must match the Vault server the URI refers to.
AWS Secrets Manager
[secrets.providers.aws]
region = "us-east-1" # optional; defaults to AWS_REGION env varEarl uses the standard AWS credential chain: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), ~/.aws/credentials, EC2 instance metadata, ECS task role, and so on. No additional configuration is needed for most deployments — if the standard chain works for your other AWS tools, it works for Earl.
The region in the URI (aws://us-east-1/...) is used directly. The region field in config is an override for when you want to centralize that rather than embed it in every URI.
GCP Secret Manager
[secrets.providers.gcp]
# No required fields.
# Uses Application Default Credentials.Run gcloud auth application-default login once to set up credentials. From there, Earl resolves URIs against the project ID embedded in the URI (gcp://my-project/...). If you're running on GCE or Cloud Run, the metadata server provides credentials automatically.
Azure Key Vault
[secrets.providers.azure]
# No required fields.
# Uses DefaultAzureCredential.Earl tries credentials in this order: environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID), workload identity, managed identity, then the Azure CLI. If az login works for your other tools, it works here too.
The vault hostname is part of the URI (azure://my-vault.vault.azure.net/...). No vault address is needed in config.
Complete example
A GitHub template that stores its token in 1Password and a database URL in Vault:
version = 1
provider = "github"
command "list_repos" {
title = "List repositories"
summary = "List repos for the authenticated user"
description = "Fetch repositories for the authenticated user."
annotations {
mode = "read"
secrets = ["op://Personal/GitHub/token"]
}
operation {
protocol = "http"
method = "GET"
url = "https://api.github.com/user/repos"
auth {
kind = "bearer"
secret = "op://Personal/GitHub/token"
}
}
result {
decode = "json"
output = "Found {{ result | length }} repos."
}
}For the full secrets reference, see Secrets & Authentication.