> ## Documentation Index
> Fetch the complete documentation index at: https://docs.codezero.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 1Password

> Use 1Password as Cordon's credential backend — setup, configuration, security model, and operational guidance.

Cordon can fetch credentials from 1Password vaults using the [1Password CLI](https://1password.com/downloads/command-line/) (`op`). This guide covers setup, configuration, the security model for agent workflows, and troubleshooting.

For the OS keyring alternative, see [Secret Sources](/configuration/secret-sources#os-keyring).

## 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:

```bash theme={null}
# macOS
brew install --cask 1password-cli

# Linux / other platforms
# See https://developer.1password.com/docs/cli/get-started/#install
```

3. An **authenticated session**:

```bash theme={null}
# Interactive sign-in (eval exports the session token to your shell)
eval $(op signin)
```

4. Verify the session works:

```bash theme={null}
op vault list
```

<Note>
  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`.
</Note>

## Configuration

### Per-route secret reference

Each route that uses 1Password specifies the vault, item, and field under `routes.auth.secret`:

```toml theme={null}
[routes.auth.secret]
source = "1password"
vault = "Engineering"
item = "Stripe API Key"
field = "secret_key"
```

| Field   | Description                                                       |
| ------- | ----------------------------------------------------------------- |
| `vault` | 1Password vault name (within the currently authenticated account) |
| `item`  | Item name within the vault                                        |
| `field` | Field label within the item (case-sensitive)                      |

<Note>
  A 1Password **vault** is a container of items within an **account** (e.g., `my-team.1password.com`). Cordon uses whichever account the `op` CLI session is currently authenticated against — there is no config field to specify which account to use. If you have multiple 1Password accounts, ensure the correct one is signed in before starting cordon. Use `op whoami` to check the active account and `op vault list` to see its vaults.
</Note>

### 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:

```toml theme={null}
[secrets]
[[secrets.providers]]
type = "1password"
path = "/opt/homebrew/bin/op"
```

<Tip>
  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.
</Tip>

### Use with routes

See [Routes](/configuration/routes) for full route examples, provider auth types, and matching behavior. You can mix 1Password and keyring sources in the same config; see [Secret Sources](/configuration/secret-sources#mixing-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:
   ```bash theme={null}
   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

```bash theme={null}
eval $(op signin)
```

Cordon requires an authenticated `op` session. 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.

## 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.

In cases where the session is reachable, an AI coding agent with shell access can run commands like:

```bash theme={null}
op read "op://Engineering/Stripe API Key/secret_key"
```

This bypasses Cordon entirely. The proxy cannot intercept or restrict local process execution.

<Warning>
  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.
</Warning>

### Mitigations

**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](/configuration/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](#prerequisites)).
3. **Add the route** with `cordon route add --source 1password` and the vault/item/field names. Prefer the CLI over hand-editing `cordon.toml`; it validates the route and writes the safer configuration for you.
4. **Start Cordon:** `cordon start` — startup validates that all configured secrets are reachable.
5. **Route traffic** through the proxy — credentials are injected transparently.

## Diagnostics

```bash theme={null}
# 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
```

<Tip>
  `cordon doctor` checks for the `op` binary on `PATH` and validates configured provider paths. Run it first when troubleshooting 1Password issues.
</Tip>

## Troubleshooting

<AccordionGroup>
  <Accordion title="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 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.
  </Accordion>

  <Accordion title="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](https://developer.1password.com/docs/cli/get-started/#install).
    * **Specify the path explicitly** in your config under `secrets.providers` (see [Provider configuration](#provider-configuration-optional)).
    * **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.
  </Accordion>

  <Accordion title="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:

    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`
  </Accordion>

  <Accordion title="Wrong secret value injected">
    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:

    ```bash theme={null}
    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`)
  </Accordion>
</AccordionGroup>
