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:- A 1Password account with a vault containing the secrets you want to inject.
-
The 1Password CLI (
op) installed:
- An authenticated session:
- Verify the session works:
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:| Field | Description |
|---|---|
vault | 1Password vault name |
item | Item name within the vault |
field | Field label within the item (case-sensitive) |
Provider configuration (optional)
By default, Cordon finds theop 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:
Complete 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:- Cordon locates the
opbinary (from the configuredpathor by searchingPATH). - 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.
- On each incoming request that matches a route with
source = "1password", Cordon runs: - The JSON response’s
valuefield is extracted and wrapped in Cordon’sSecrettype, which zeroizes memory on drop and redacts on all log output. - If
opbecomes unresponsive or 1Password is locked during a request, the affected request receives a 503 error.
Authentication methods
Interactive sign-in
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
Authorizationheader value (stripped and replaced unconditionally for matched routes)
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
opCLI communicates with the 1Password desktop app over a local socket. While the app is unlocked, any same-user process can invokeopand 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.
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 callingsecurity 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
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 toop — 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
- Store the secret in 1Password (create a vault, item, and field if needed).
- Install and authenticate the
opCLI (see Prerequisites). - Configure the route in
cordon.tomlwithsource = "1password"and the vault/item/field names. - Start Cordon:
cordon start— startup validates that all configured secrets are reachable. - Route traffic through the proxy — credentials are injected transparently.
Diagnostics
Troubleshooting
op exited with status 1
op exited with status 1
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.
op error output directly to the terminal — read the error message for specifics.1Password CLI (op) not found
1Password CLI (op) not found
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:
launchdandsystemdservices have a minimalPATH. Use an explicitpathin the provider config, or add theopdirectory to the service’sPATH.
which op in your shell to find the binary location.Secrets not updating after rotation
Secrets not updating after rotation
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:
- Verify the rotation completed in 1Password:
op item get "Item Name" --vault "Vault Name" --format json - Check that
opis authenticated and returning the new value. - If the issue persists, restart Cordon:
cordon stop && cordon start
Service account not working
Service account not working
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
EnvironmentVariablesentry to the plist. - systemd: Use
systemctl --user edit cordon-<NAME>.serviceto add anEnvironment=line in the override, then restart the service withsystemctl --user restart cordon-<NAME>.service. Find your service name withcordon status list.
Wrong secret value injected
Wrong secret value injected
The Look for 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:label property on each field in the JSON output. Common mistakes:- Using
passwordinstead ofcredential(or vice versa) - Using a section name instead of a field label
- Case mismatch (e.g.,
Secret_Keyvssecret_key)