Skip to main content
Cordon can fetch credentials from 1Password vaults using the 1Password CLI (op). This guide covers setup, configuration, the security model for agent workflows, and troubleshooting. For the OS keyring alternative, see Secret Sources.

Prerequisites

Before configuring 1Password as a secret source, you need:
  1. A 1Password account with a vault containing the secrets you want to inject.
  2. The 1Password CLI (op) installed:
# macOS
brew install --cask 1password-cli

# Linux / other platforms
# See https://developer.1password.com/docs/cli/get-started/#install
  1. An authenticated session:
# Interactive sign-in (eval exports the session token to your shell)
eval $(op signin)
  1. Verify the session works:
op vault list
Cordon does not handle 1Password authentication. The op CLI must have a valid session before cordon start. If the session expires while Cordon is running, affected requests will receive 503 errors until you re-authenticate with op.

Configuration

Per-route secret reference

Each route that uses 1Password specifies the vault, item, and field:
[[routes]]
name = "anthropic"

[routes.match]
host = "api.anthropic.com"

[routes.auth]
type = "bearer"

[routes.auth.secret]
source = "1password"
vault = "Engineering"
item = "Anthropic API Key"
field = "credential"
FieldDescription
vault1Password vault name
itemItem name within the vault
fieldField label within the item (case-sensitive)

Provider configuration (optional)

By default, Cordon finds the op binary by searching your PATH. If op is not on PATH — common when running as a background service via launchd or systemd — specify the path explicitly:
[secrets]
[[secrets.providers]]
type = "1password"
path = "/opt/homebrew/bin/op"
Find the full path to your op binary with which op. Background services often have a minimal PATH that doesn’t include Homebrew or user-installed binaries.

Complete example

listen = 6790

[tls]
enabled = true
# Substitute paths from your real `cordon.toml` (typically under ~/.config/cordon/projects/<namespace>/certs/).
ca_cert_path = "/path/to/ca-cert.pem"
ca_key_path = "/path/to/ca-key.pem"

[secrets]
[[secrets.providers]]
type = "1password"
path = "/opt/homebrew/bin/op"

[[routes]]
name = "stripe"

[routes.match]
host = "api.stripe.com"

[routes.auth]
type = "bearer"

[routes.auth.secret]
source = "1password"
vault = "Engineering"
item = "Stripe API Key"
field = "secret_key"

[[routes]]
name = "anthropic"

[routes.match]
host = "api.anthropic.com"

[routes.auth]
type = "bearer"

[routes.auth.secret]
source = "1password"
vault = "Engineering"
item = "Anthropic API Key"
field = "credential"
You can mix 1Password and keyring sources in the same config. See Secret Sources for an example.

How it works

For HTTP routes, Cordon fetches 1Password secrets per-request (just-in-time), not at startup. PostgreSQL listeners currently resolve their 1Password secrets at startup and reuse them until Cordon restarts:
  1. Cordon locates the op binary (from the configured path or by searching PATH).
  2. At startup, Cordon performs a dry-run validation — it fetches each configured secret once to verify that sources are reachable and credentials resolve. The fetched values are discarded immediately.
  3. On each incoming request that matches a route with source = "1password", Cordon runs:
    op item get "<item>" --vault "<vault>" --fields "label=<field>" --format json
    
  4. The JSON response’s value field is extracted and wrapped in Cordon’s Secret type, which zeroizes memory on drop and redacts on all log output.
  5. If op becomes unresponsive or 1Password is locked during a request, the affected request receives a 503 error.
Rotation is automatic for HTTP routes. When you rotate a credential in 1Password, the new value takes effect on the next matching request — no restart required. PostgreSQL listeners currently need a Cordon restart to pick up rotated values.

Authentication methods

Interactive sign-in

eval $(op signin)
Suitable for development sessions. The session may expire after a period of inactivity (controlled by 1Password’s settings). If the session expires before Cordon starts, startup validation will fail — re-authenticate and try again. If the session expires while Cordon is running, affected requests will receive 503 errors until you re-authenticate.

Non-interactive / CI environments

For headless or CI environments where interactive sign-in is not possible, see the 1Password CI/CD integrations for secure approaches that avoid exporting tokens into the shell environment.

Security considerations

What Cordon protects

When using Cordon with 1Password, credentials are injected at the HTTP layer — the agent process never handles secret values. Specifically, Cordon keeps the following out of the agent’s environment variables, logs, and request headers:
  • The credential value (injected after the request leaves the agent’s process)
  • Any Authorization header value (stripped and replaced unconditionally for matched routes)
Vault names, item names, and field names are configuration metadata, not credentials. Cordon does not inject them into the agent’s environment or expose them through the proxy. However, these values are present in cordon.toml on disk — a same-user agent with file or shell access could read the config file and discover them. This is a limitation of the local same-user trust model, not credential exposure.

The op CLI attack surface

Cordon invokes the op CLI as a subprocess. Whether other processes can reuse that authenticated session depends on how op is configured:
  • Desktop app integration (biometric): The op CLI communicates with the 1Password desktop app over a local socket. While the app is unlocked, any same-user process can invoke op and authenticate through the same socket.
  • Shell-based sign-in (eval $(op signin)): The session token is stored in a shell environment variable (OP_SESSION_*). Only processes that inherit that variable can reuse it — other terminals and independently launched processes cannot.
  • Service accounts: Only processes with the service account token can authenticate. See the 1Password Service Accounts docs for secure provisioning.
In cases where the session is reachable, an AI coding agent with shell access can run commands like:
op read "op://Engineering/Stripe API Key/secret_key"
This bypasses Cordon entirely. The proxy cannot intercept or restrict local process execution.
This side channel is not introduced by Cordon — it is inherent to any setup where the op CLI is installed and an authenticated session is reachable. Cordon reduces the attack surface by keeping credentials out of environment variables, logs, and the agent’s context, but it cannot fully mediate local CLI access to the vault.

Mitigations

Scope service account access. If using a service account, grant it access only to the specific vault items Cordon needs. A compromised agent can only reach items the service account can access. Consider the OS keyring on macOS. macOS Keychain enforces per-application access control lists (ACLs) on keychain entries. By default, only the binary that created an entry can read it silently — other binaries trigger a system authorization dialog requiring user approval. This means an agent process calling security find-generic-password to read an entry created by the cordon binary would prompt the user visibly, providing an opportunity to deny access. See Secret Sources — OS Keyring. Restrict agent shell access where possible. Some agents support permission modes that limit arbitrary command execution:
  • Claude Code’s permission system can restrict tool use
  • Codex offers a sandboxed execution mode
  • IDE-based agents (Cursor) can limit terminal access through configuration
Monitor 1Password audit logs. 1Password Teams and Business plans provide audit logs that record op CLI access. Unexpected access patterns may indicate an agent attempting to read vault items directly.

Future direction

A direct SDK integration — compiling 1Password access into Cordon’s binary rather than shelling out to op — is planned. This would eliminate the external CLI as an attack vector by keeping the authenticated session inside Cordon’s process memory, inaccessible to other processes. This integration does not exist today; the op CLI is the current integration path.

Workflow

  1. Store the secret in 1Password (create a vault, item, and field if needed).
  2. Install and authenticate the op CLI (see Prerequisites).
  3. Configure the route in cordon.toml with source = "1password" and the vault/item/field names.
  4. Start Cordon: cordon start — startup validates that all configured secrets are reachable.
  5. Route traffic through the proxy — credentials are injected transparently.

Diagnostics

# Verify Cordon can find and use the op binary
cordon doctor

# Verify op session is active
op vault list

# Verify which account is signed in
op whoami

# Inspect a specific item's fields (to verify field labels)
op item get "Stripe API Key" --vault "Engineering" --format json
cordon doctor checks for the op binary on PATH and validates configured provider paths. Run it first when troubleshooting 1Password issues.

Troubleshooting

The op CLI returned an error. Common causes:
  • Session expired: Re-authenticate with eval $(op signin). HTTP routes fetch secrets per-request, so new matching requests will use the refreshed session automatically. PostgreSQL listeners require a Cordon restart after re-authentication.
  • Vault not found: Verify the vault name matches exactly (case-sensitive). Check with op vault list.
  • Item not found: Verify the item name matches exactly. Check with op item list --vault "VaultName".
  • Permission denied: The signed-in account or service account lacks access to the vault. Check vault permissions in the 1Password app.
Cordon passes op error output directly to the terminal — read the error message for specifics.
The op binary is not on PATH. Solutions:
  • Install it: brew install --cask 1password-cli (macOS) or see the 1Password CLI install guide.
  • Specify the path explicitly in your config under secrets.providers (see Provider configuration).
  • Background services: launchd and systemd services have a minimal PATH. Use an explicit path in the provider config, or add the op directory to the service’s PATH.
Run which op in your shell to find the binary location.
For HTTP routes, Cordon fetches secrets per-request, so rotated credentials in 1Password should take effect on the next matching request automatically. PostgreSQL listeners resolve credentials at startup and require a restart after rotation. If a stale value is still being injected:
  1. Verify the rotation completed in 1Password: op item get "Item Name" --vault "Vault Name" --format json
  2. Check that op is authenticated and returning the new value.
  3. If the issue persists, restart Cordon: cordon stop && cordon start
The service account token must be available in the environment where Cordon runs. See the 1Password Service Accounts docs for secure provisioning.
  • launchd: Add an EnvironmentVariables entry to the plist.
  • systemd: Use systemctl --user edit cordon-<NAME>.service to add an Environment= line in the override, then restart the service with systemctl --user restart cordon-<NAME>.service. Find your service name with cordon status list.
The field value in your config must match the field label in 1Password exactly (case-sensitive).Inspect the item’s fields to find the correct label:
op item get "Item Name" --vault "Vault Name" --format json
Look for the label property on each field in the JSON output. Common mistakes:
  • Using password instead of credential (or vice versa)
  • Using a section name instead of a field label
  • Case mismatch (e.g., Secret_Key vs secret_key)