# cordon doctor Source: https://docs.codezero.io/cli/doctor Diagnose common setup issues. Runs a series of checks to diagnose common setup problems. ## Usage ```bash theme={null} cordon doctor [--config PATH | --scope project|user] ``` | Option | Description | | ---------------- | ---------------------------------------------------------------------------------------------- | | `--config`, `-c` | Path to config file. Mutually exclusive with `--scope`. | | `--scope` | `project` or `user`; selects which config file to inspect. Mutually exclusive with `--config`. | ## Checks performed | Check | What it verifies | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | Config validity | `cordon.toml` parses and validates correctly | | Cert paths | CA cert and key files exist at configured paths | | Cert expiry | CA certificate is not expired | | Trust store | CA is trusted in the system trust store | | Port availability | Configured listen port is available | | Service status | Background service is running (if installed) | | OS keyring session (Linux) | D-Bus session bus is available for keyring secrets | | Secret providers | Configured secret providers are detected | | Service PATH (macOS) | Background service PATH matches current session | | Token enforcement | `tokens.toml` exists and contains valid tokens for the scope | | Agent skill freshness | Installed agent skill files match expected content and permissions | | Stale service binary | Running service is older than the installed binary (shows restart command) | | Go tool network isolation (macOS) | Go-based tools (e.g. `gh`, `terraform`, `kubectl`) have the network-isolation setting needed to route through Cordon | ### Installed services Doctor partitions installed services into: * **Project services**: installed services whose config path matches the current config * **Other installed services**: services that point at some other config path Project service failures affect the overall pass/fail result. Other installed services that are healthy (config file still exists) are shown for visibility, and their running-state failures are informational only. **Orphaned services** — installed services whose embedded `--config` path no longer exists on disk — are flagged as errors and contribute to doctor's overall error count. These accumulate when project directories are deleted or moved. Run [`cordon service cleanup`](/cli/service#cordon-service-cleanup) to review and remove them. If `./cordon.toml` is missing and exactly one installed service exists, `cordon doctor` inspects that service automatically. If multiple installed services exist, re-run with `--config` for the target config. ### Linux keyring session check On Linux, if any routes use `source: keyring`, doctor checks whether a D-Bus session bus is available — either via `DBUS_SESSION_BUS_ADDRESS` or the systemd user bus socket (`$XDG_RUNTIME_DIR/bus`). This detects environments where the Secret Service provider is unreachable (containers, CI, SSH, headless servers) before you hit a runtime error. ### Agent skill freshness Doctor probes the plausible agent skill install locations for each integration: both project-scope and user-scope paths for `claude-code` and `codex`, the single `~/.hermes/skills/devops/cordon/SKILL.md` path for `hermes`. OpenClaw skills are not currently probed. Only actually-installed skills are checked — missing files are silently skipped, so integrations you haven't set up won't appear. For each installed skill file, doctor verifies: * The file is a regular file (not a symlink or directory) * Content matches the expected version embedded in the binary * Permissions are `0444` (read-only) A content mismatch or incorrect permissions produces a warning with a `cordon setup ` remediation hint. ## Telemetry After diagnostics finish, `cordon doctor` records a best-effort telemetry event bucketed by result: `doctor_ok`, `doctor_warning`, or `doctor_error`. This helps identify whether users are running into setup trouble without sending individual check messages, config paths, service names, routes, or credential data. Doctor sends an existing install ID when one is already present, but it does not create the install ID before the first `cordon start` install event. See [Telemetry and update checks](/security/overview#telemetry-and-update-checks) for the full list of collection points and opt-outs. ## Example output ``` cordon doctor ✓ Config: /home/user/my-project/cordon.toml ✓ CA certificate: /home/user/.config/cordon/projects/.../ca-cert.pem ✓ CA key: /home/user/.config/cordon/projects/.../ca-key.pem ✓ CA certificate is valid PEM and not expired ✓ CA is in system trust store ✓ Port 8432: available ✓ Service 'my-project': running (PID 12345, launchd (io.codezero.cordon.my-project)) ✓ Secret providers: not configured (resolved on-demand) ✓ Service 'my-project': PATH is current ✓ Token enforcement: active (1 token(s) configured) ✓ Agent skill (claude-code): /home/user/my-project/.claude/skills/cordon/SKILL.md — up to date Other installed services: ✓ Service 'hermes': running (PID 67890, launchd (io.codezero.cordon.hermes), config: /path/to/hermes/cordon.toml) ✓ Service 'hermes': PATH is current 11 passed · 0 warnings · 0 errors ``` The port shown above is illustrative. Run `cordon status` to see the port allocated for the selected config. Run `cordon doctor` as the first troubleshooting step when something isn't working. # cordon env Source: https://docs.codezero.io/cli/env Print scope-aware env vars to route traffic through cordon. Print the environment variables a consumer needs to route traffic through cordon. Use the output in your shell, `.envrc` (direnv), `.mise.toml`, or any tooling that consumes env vars. ## Usage ```bash theme={null} cordon env [OPTIONS] ``` ## Options | Option | Description | | ---------------- | ---------------------------------------------------------- | | `--scope` | Configuration scope: `project` (default) or `user` | | `--config`, `-c` | Path to `cordon.toml` (overrides scope-resolved path) | | `--format` | Output format: `shell` (default), `fish`, `dotenv`, `json` | Unlike most config-reading commands, `--config` does not conflict with `--scope`; when provided, `--config` wins. ## Examples Interactive shell (bash / zsh): ```bash theme={null} eval "$(cordon env)" ``` fish: ```fish theme={null} cordon env --format fish | source ``` direnv (`.envrc`): ```bash theme={null} eval "$(cordon env)" ``` mise (`.mise.toml`): ```bash theme={null} cordon env --format dotenv > .mise.cordon.env ``` JSON for scripting: ```bash theme={null} cordon env --format json | jq .HTTPS_PROXY ``` ## Output Vars emitted: * `HTTPS_PROXY` / `HTTP_PROXY` (plus lowercase) — `http://:@127.0.0.1:` when token enforcement is active, otherwise `http://127.0.0.1:` with a warning. `` is Cordon's local scope namespace, not an OAuth/OIDC audience claim. * `NODE_EXTRA_CA_CERTS` — absolute path to the Cordon CA cert. Node.js expects a raw cert, not a bundle. * `SSL_CERT_FILE` / `REQUESTS_CA_BUNDLE` / `CURL_CA_BUNDLE` — absolute path to the combined (system + Cordon) CA bundle. These env vars *replace* the default trust store, so a bundle is required. * JSON output is a flat object of environment variable names to string values, preserving compatibility with scripts that export every top-level entry. Authenticated proxy URLs contain Cordon tokens. Do not commit or share them. See [Proxy Tokens](/guides/token-migration) for rotation and handling guidance. See the [generic tool guide](/guides/generic) for where to apply these values and [SDK Compatibility](/guides/sdk-compatibility#runtime-ca-and-proxy-notes) for runtime-specific exceptions. ## Errors * `no cordon.toml at — run \`cordon setup\` first\` — no config at the resolved scope path. * Bundle vars are omitted (with a comment) if `combined-ca.pem` is missing; run `cordon setup` to generate it. ## See also * [`cordon setup`](/cli/setup) — generates the config and CA bundle this command depends on. * [`cordon token`](/cli/token) — manage proxy authentication tokens. * [Any tool (generic)](/guides/generic) — manual proxy environment setup. # cordon help Source: https://docs.codezero.io/cli/help Show help for cordon or a specific subcommand. Print the same help text as `--help`, including nested subcommands. ## Usage ```bash theme={null} cordon help [SUBCOMMAND...] ``` ## Examples ```bash theme={null} # Top-level help cordon help # Help for a command group cordon help route # Help for a nested command cordon help route add ``` Use `cordon help route add`, not `cordon route help add`. # cordon integration Source: https://docs.codezero.io/cli/integration Enable or disable integrations without re-running full setup. Manage integrations independently of the base setup. Enable wires an integration to use cordon (running base setup if needed); disable removes only integration-specific settings. ## cordon integration enable Enable an integration. Runs the base setup if needed, then configures that integration's proxy settings. ```bash theme={null} cordon integration enable [OPTIONS] ``` Flags go after the integration name, for example `cordon integration enable codex --scope user`. | Argument / Option | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | | `` | `claude-code`, `codex`, `hermes`, or `openclaw` | | `--scope` | Configuration scope for Claude Code and Codex: `project` (default) or `user`. Hermes and OpenClaw do not expose `--scope`. | | `--config`, `-c` | Path to `cordon.toml` (defaults to the selected integration's config path) | | `--yes` | Skip confirmation prompts | | `--trust` | Add CA to system trust store | | `--no-trust` | Skip trust store prompt | | `--regenerate-ca` | Regenerate the CA certificate | | `--service` | Install cordon as a background service | | `--no-service` | Skip the service install prompt | ### Default config targets Each integration has a default config target that matches its typical usage pattern: | Integration | Default config target | | ------------- | ----------------------------------------------- | | `claude-code` | Project scope: `./cordon.toml` | | `codex` | Project scope: `./cordon.toml` | | `hermes` | Project-style config: `~/.hermes/cordon.toml` | | `openclaw` | Project-style config: `~/.openclaw/cordon.toml` | Override Claude Code or Codex with `--scope`: ```bash theme={null} # Use user scope for claude-code instead of the default project scope cordon integration enable claude-code --scope user ``` Hermes and OpenClaw do not expose `--scope`; use `--config` if you need to override the default cordon config path for either integration. The user-scope config (`~/.config/cordon/cordon.toml`) is rejected for Hermes/OpenClaw setup and enable. ### Examples ```bash theme={null} # Enable Claude Code integration (project scope) cordon integration enable claude-code # Enable Codex integration with trust and service install cordon integration enable codex --trust --service # Enable Hermes integration (uses ~/.hermes/cordon.toml) cordon integration enable hermes # Enable OpenClaw integration (uses ~/.openclaw/cordon.toml) cordon integration enable openclaw ``` ## cordon integration disable Disable an integration. Removes only the settings that the integration wrote — keeps the CA files and `cordon.toml` so you can re-enable without rotating certificates. ```bash theme={null} cordon integration disable [OPTIONS] ``` Flags go after the integration name, for example `cordon integration disable claude-code --yes`. | Argument / Option | Description | | ----------------- | -------------------------------------------------- | | `` | `claude-code`, `codex`, `hermes`, or `openclaw` | | `--scope` | Configuration scope for Claude Code and Codex only | | `--config`, `-c` | Path to `cordon.toml` | | `--yes` | Skip confirmation prompts | | `--trust` | Also remove the CA from the system trust store | ### Examples ```bash theme={null} # Disable Claude Code integration cordon integration disable claude-code # Disable and remove CA from trust store cordon integration disable codex --trust # Disable Hermes integration (uses ~/.hermes/cordon.toml, or legacy user config if applicable) cordon integration disable hermes # Disable OpenClaw integration (uses ~/.openclaw/cordon.toml, or legacy user config if applicable) cordon integration disable openclaw # Skip confirmation cordon integration disable claude-code --yes ``` Disabling an integration is safe — it only removes integration-specific wiring. The CA certificate, key, and `cordon.toml` are retained so you can re-enable with `cordon integration enable` without regenerating anything. ## What each command touches | Command | Creates / modifies | Removes | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | | `cordon setup` | CA cert + key, `cordon.toml` | — | | `cordon setup claude-code` | CA cert + key, `cordon.toml`, Claude Code settings file (`settings.local.json` for project scope, `settings.json` for user scope) env vars | — | | `cordon integration enable claude-code` | Same as `cordon setup claude-code` | — | | `cordon integration disable claude-code` | — | Claude Code settings file env vars | | `cordon integration disable claude-code --trust` | — | Claude Code settings file env vars + CA from system trust store | | `cordon setup codex` | CA cert + key, `cordon.toml`, Codex `.env` and `config.toml` (project `.codex/` by default, `$CODEX_HOME`/`~/.codex` for user scope) | — | | `cordon integration enable codex` | Same as `cordon setup codex` | — | | `cordon integration disable codex` | — | Codex `.env` env vars | | `cordon setup hermes` | CA cert + key, `~/.hermes/cordon.toml`, combined CA bundle, Hermes `~/.hermes/.env` env vars | — | | `cordon integration enable hermes` | Same as `cordon setup hermes` | — | | `cordon integration disable hermes` | — | Hermes `.env` env vars + combined CA bundle | | `cordon setup openclaw` | CA cert + key, `~/.openclaw/cordon.toml`, OpenClaw `~/.openclaw/openclaw.json` proxy settings, daemon service env, agent skill | — | | `cordon integration enable openclaw` | Same as `cordon setup openclaw` | — | | `cordon integration disable openclaw` | — | OpenClaw proxy settings, daemon service env, combined CA bundle, agent skill | | `cordon trust` | Adds CA to system trust store | — | | `cordon untrust` | — | CA from system trust store | | `cordon service uninstall NAME` | — | launchd plist / systemd unit | | `cordon setup --regenerate-ca` | Replaces existing CA cert + key | Old cert + key | If you run `--regenerate-ca` and the old CA was already trusted, rerun `cordon trust` after setup. Regeneration replaces the certificate files, but it does not automatically update the system trust store to trust the new CA. # cordon listener Source: https://docs.codezero.io/cli/listener Add, edit, list, show, and remove PostgreSQL listeners in cordon.toml. Manage the PostgreSQL listeners that tell cordon which database connections to intercept and what credentials to inject. Prefer these commands over editing `cordon.toml` directly. They validate listener ports, upstreams, TLS mode, and secret references, and are intended to make listener configuration safer and less error-prone. All `cordon listener` subcommands accept `--scope project|user` to select which config file they target, matching `cordon start` and `cordon setup`. `--config` and `--scope` are mutually exclusive — passing both is an error. Resolution: 1. `--config ` — explicit path. 2. `--scope ` — resolves to the scope's default config path (project → `$CWD/cordon.toml`, user → `~/.config/cordon/cordon.toml`). 3. Neither flag — defaults to project scope (`$CWD/cordon.toml`). `cordon listener add --config ` can create a default config skeleton when that explicit path is missing. With default project scope or `--scope`, run `cordon setup` first. Other listener subcommands require the resolved config file to already exist. ## cordon listener add Add a new PostgreSQL listener. With no flags, launches an interactive wizard that prompts for name, port, upstream address, client TLS mode, username, secret source, and source-specific fields. ```bash theme={null} cordon listener add ``` ### Non-interactive mode Pass flags to skip the wizard: ```bash theme={null} cordon listener add --name local-pg --port 15432 --upstream db.example.com:5432 \ --username app_user --source keyring --account pg-password ``` | Flag | Description | | -------------- | -------------------------------------------------------------------------------------------------- | | `--name` | Listener name | | `--port` | Local port to listen on (e.g. `15432`) | | `--upstream` | Upstream PostgreSQL address (e.g. `db.example.com:5432`) | | `--username` | Database username | | `--source` | `1password` or `keyring` | | `--vault` | 1Password vault name (requires `--source 1password`) | | `--item` | 1Password item name (requires `--source 1password`) | | `--field` | 1Password field name (requires `--source 1password`) | | `--account` | 1Password account selector with `--source 1password`; keyring account name with `--source keyring` | | `--client-tls` | Client-to-Cordon TLS mode: `accept` (default), `require`, or `disable` | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | `--client-tls require` requires valid `tls.ca_cert_path` and `tls.ca_key_path` in `cordon.toml`. When `client_tls` is omitted, Cordon treats it as `accept`. Cordon always requires TLS from Cordon to the upstream PostgreSQL server before it sends startup or authentication data. There is no command-line or configuration option to disable upstream PostgreSQL TLS. ### Examples ```bash theme={null} # Interactive — wizard prompts for everything cordon listener add # Keyring-backed listener cordon listener add --name local-pg --port 15432 \ --upstream db.example.com:5432 --username app_user \ --source keyring --account pg-password # Require local PostgreSQL client TLS cordon listener add --name secure-pg --port 15435 \ --upstream db.example.com:5432 --username app_user \ --source keyring --account pg-password --client-tls require # 1Password-backed listener cordon listener add --name staging-db --port 15433 \ --upstream staging-db.internal:5432 --username deploy \ --source 1password --account my-team --vault Infrastructure --item "Staging DB" --field password # Add to the user-scope config cordon listener add --scope user --name shared-pg --port 15434 \ --upstream shared.internal:5432 --username reader \ --source keyring --account shared-pg-password ``` After adding a keyring-backed listener, store the secret with `cordon secret set ACCOUNT`. Use `cordon listener show NAME` to find the keyring account. 1Password listeners don't need this step — credentials are fetched from 1Password directly. ## cordon listener edit Edit an existing listener. With no flags (other than `--scope` or `--config`), launches an interactive editor that pre-fills current values — press Enter to keep a value, or type a new one. ```bash theme={null} cordon listener edit NAME ``` ### Non-interactive mode Pass flags to change only specific fields without prompting: ```bash theme={null} cordon listener edit local-pg --port 15433 ``` | Argument / Flag | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NAME` | Listener name to edit (positional, required) | | `--new-name` | Rename the listener | | `--port` | New local port | | `--upstream` | New upstream address | | `--username` | New database username | | `--source` | `1password` or `keyring` | | `--vault` | 1Password vault name (requires `--source 1password` or existing 1password source) | | `--item` | 1Password item name (requires `--source 1password` or existing 1password source) | | `--field` | 1Password field name (requires `--source 1password` or existing 1password source) | | `--account` | 1Password account selector with `--source 1password` or an existing 1Password source; keyring account name with `--source keyring` or an existing keyring source | | `--client-tls` | Client-to-Cordon TLS mode: `accept`, `require`, or `disable` | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | ### Partial updates Non-interactive edit changes only the fields you specify. Unspecified fields keep their current values. Partial 1Password updates work the same as for routes: ```bash theme={null} # Change only the vault — item and field stay the same cordon listener edit staging-db --vault NewVault ``` ### Switching secret sources When switching from one source to another with `--source`: * **1password to keyring**: `--account` defaults to the listener name if omitted * **keyring to 1password**: requires `--vault`, `--item`, and `--field` ### Examples ```bash theme={null} # Interactive — pre-fills current values cordon listener edit local-pg # Change just the port cordon listener edit local-pg --port 15433 # Rename a listener cordon listener edit local-pg --new-name app-db # Change the upstream address cordon listener edit local-pg --upstream new-db.internal:5432 # Require local PostgreSQL client TLS cordon listener edit local-pg --client-tls require # Switch source to keyring (account defaults to "local-pg") cordon listener edit local-pg --source keyring # Edit a listener in the user-scope config cordon listener edit shared-pg --scope user ``` ## cordon listener list List all configured listeners. ```bash theme={null} cordon listener list [--scope project|user] [--config path/to/cordon.toml] ``` | Option | Description | | ---------- | ----------------------------------------------------------------- | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to read | ```bash theme={null} # List listeners in the user-scope config cordon listener list --scope user ``` ## cordon listener show Show details of a single listener. ```bash theme={null} cordon listener show NAME [--scope project|user] [--config path/to/cordon.toml] ``` | Argument / Option | Description | | ----------------- | ----------------------------------------------------------------- | | `NAME` | Listener name to show | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to read | ## cordon listener remove Remove a listener by name. ```bash theme={null} cordon listener remove NAME [--yes] [--scope project|user] [--config path/to/cordon.toml] ``` | Argument / Option | Description | | ----------------- | ----------------------------------------------------------------- | | `NAME` | Listener name to remove | | `--yes`, `-y` | Skip confirmation prompt | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | # cordon route Source: https://docs.codezero.io/cli/route Add, edit, list, show, and remove routes in cordon.toml. Manage the routes that tell cordon which hosts to intercept and what credentials to inject. Prefer these commands over editing `cordon.toml` directly. They validate the route shape, preserve existing config, and are intended to make route configuration safer and less error-prone. All `cordon route` subcommands accept `--scope project|user` to select which config file they target, matching `cordon start` and `cordon setup`. `--config` and `--scope` are mutually exclusive — passing both is an error. Resolution: 1. `--config ` — explicit path. 2. `--scope ` — resolves to the scope's default config path (project → `$CWD/cordon.toml`, user → `~/.config/cordon/cordon.toml`). 3. Neither flag — defaults to project scope (`$CWD/cordon.toml`). `cordon route add --config ` can create a default config skeleton when that explicit path is missing. With default project scope or `--scope`, run `cordon setup` first. Other route subcommands require the resolved config file to already exist. ## cordon route add Add a new route. With no flags, launches an interactive wizard that prompts for route match scope, auth, and secret fields. ```bash theme={null} cordon route add ``` ### Non-interactive mode Pass flags to skip the wizard. Requires `--host`, `--auth-type`, `--source`, and the fields required by the selected auth type and secret source: ```bash theme={null} cordon route add --host api.stripe.com --auth-type header --header-name Authorization --source keyring --account stripe-key ``` | Flag | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--host` | Hostname to match (e.g. `api.stripe.com`) | | `--name` | Route name (defaults to derived from host) | | `--auth-type` | `header` or `basic` | | `--source` | `1password` or `keyring` | | `--vault` | 1Password vault name (requires `--source 1password`) | | `--item` | 1Password item name (requires `--source 1password`) | | `--field` | 1Password field name (requires `--source 1password`) | | `--account` | 1Password account selector with `--source 1password` (sign-in address, shorthand, account UUID, or user UUID — run `op account list` to find valid values); keyring account name with `--source keyring` | | `--username` | Username (requires `--auth-type basic`) | | `--header-name` | Header name (requires `--auth-type header`; `Authorization` routes use the `Bearer` scheme automatically) | | `--path-exact` | Match one exact normalized path, e.g. `/v1/comments`; mutually exclusive with `--path-glob` | | `--path-glob` | Match variable path segments with `*` and `**`, e.g. `/v1/*/comments/**`; mutually exclusive with `--path-exact` | | `--method` | Limit injection to HTTP methods; repeat or comma-separate values | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | ### Examples ```bash theme={null} # Interactive — wizard prompts for route match scope, auth, and secret fields cordon route add # Bearer-style Authorization header with keyring cordon route add --host api.openai.com --auth-type header --header-name Authorization --source keyring --account openai # Raw API-key header with 1Password cordon route add --host api.anthropic.com --auth-type header --header-name x-api-key \ --source 1password --account my-team --vault Engineering --item "Anthropic API Key" --field credential # Basic auth with keyring cordon route add --host db.example.com --auth-type basic --username admin \ --source keyring --account db-password # Custom route name cordon route add --host api.stripe.com --name stripe-live \ --auth-type header --header-name Authorization --source keyring --account stripe-live-key # Scoped route for a subtree and method set cordon route add --host api.stripe.com --path-glob '/v1/**' \ --method POST --method PATCH \ --auth-type header --header-name Authorization --source keyring --account stripe-write-key # Scoped route with variable path segments cordon route add --host api.stripe.com --path-glob '/v1/*/comments/**' \ --auth-type header --header-name Authorization --source keyring --account stripe-comments-key # Add to the user-scope config (~/.config/cordon/cordon.toml) cordon route add --scope user --host api.openai.com \ --auth-type header --header-name Authorization --source keyring --account openai ``` After adding a keyring-backed route, store the secret with `cordon secret set ACCOUNT`. Use `cordon route show NAME` to find the keyring account for a route. 1Password routes don't need this step — credentials are fetched from 1Password directly. The interactive wizard infers `path_exact` vs `path_glob` from input syntax: a value containing `*` or `**` becomes `path_glob`, anything else becomes `path_exact`. To configure a `path_exact` value that contains a literal `*` (rare — e.g. when matching a request path that includes percent-encoded `%2A`), use the `--path-exact` flag instead of the wizard. The 0.4.0 `--path-prefix` flag is still accepted for compatibility but hidden from help. Prefer `--path-glob '/v1/**'` for new routes. Legacy prefixes are converted to glob syntax only when the conversion preserves the same match behavior. ## cordon route edit Edit an existing route. With no flags (other than `--scope` or `--config`), launches an interactive editor that pre-fills current values — press Enter to keep a value, or type a new one. ```bash theme={null} cordon route edit NAME ``` ### Non-interactive mode Pass flags to change only specific fields without prompting: ```bash theme={null} cordon route edit stripe --host api2.stripe.com ``` | Argument / Flag | Description | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NAME` | Route name to edit (positional, required) | | `--new-name` | Rename the route | | `--host` | New hostname | | `--auth-type` | `header` or `basic` | | `--source` | `1password` or `keyring` | | `--vault` | 1Password vault name (requires `--source 1password` or existing 1password source) | | `--item` | 1Password item name (requires `--source 1password` or existing 1password source) | | `--field` | 1Password field name (requires `--source 1password` or existing 1password source) | | `--account` | 1Password account selector with `--source 1password` or an existing 1Password source (sign-in address, shorthand, account UUID, or user UUID — run `op account list` to find valid values); keyring account name with `--source keyring` or an existing keyring source | | `--username` | Username (requires `--auth-type basic`) | | `--header-name` | Header name (requires `--auth-type header`; `Authorization` routes use the `Bearer` scheme automatically when the header is changed with the CLI) | | `--path-exact` | Set an exact-path matcher; mutually exclusive with `--path-glob` | | `--path-glob` | Set a path-glob matcher; mutually exclusive with `--path-exact` | | `--clear-path` | Clear any configured path matcher | | `--method` | Replace the HTTP method scope; repeat or comma-separate values | | `--clear-methods` | Clear the HTTP method scope | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | ### Partial updates Non-interactive edit changes only the fields you specify. Unspecified fields keep their current values. For 1Password sources, you can update individual sub-fields without repeating the others: ```bash theme={null} # Change only the vault — item and field stay the same cordon route edit stripe --vault NewVault ``` ### Switching secret sources When switching from one source to another with `--source`: * **1password to keyring**: `--account` defaults to the route name if omitted * **keyring to 1password**: requires `--vault`, `--item`, and `--field` ```bash theme={null} # Switch from 1password to keyring (account defaults to "stripe") cordon route edit stripe --source keyring # Switch from keyring to 1password cordon route edit stripe --source 1password --vault Eng --item "Stripe" --field token ``` ### Examples ```bash theme={null} # Interactive — pre-fills current values, press Enter to keep cordon route edit stripe # Change just the host cordon route edit stripe --host api2.stripe.com # Change route scope cordon route edit stripe --path-exact /v1/comments --method POST # Clear route scope cordon route edit stripe --clear-path --clear-methods # Rename a route cordon route edit stripe --new-name stripe-live # Switch auth type (basic requires --username) cordon route edit stripe --auth-type basic --username admin # Update keyring account cordon route edit stripe --account new-stripe-key # Edit a route in the user-scope config cordon route edit stripe --scope user ``` ## cordon route list List all configured routes. ```bash theme={null} cordon route list [--scope project|user] [--config path/to/cordon.toml] ``` | Option | Description | | ---------- | ----------------------------------------------------------------- | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to read | ```bash theme={null} # List routes in the user-scope config cordon route list --scope user ``` ## cordon route show Show details of a single route. ```bash theme={null} cordon route show NAME [--scope project|user] [--config path/to/cordon.toml] ``` | Argument / Option | Description | | ----------------- | ----------------------------------------------------------------- | | `NAME` | Route name to show | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to read | ## cordon route remove Remove a route by name. ```bash theme={null} cordon route remove NAME [--yes] [--scope project|user] [--config path/to/cordon.toml] ``` | Argument / Option | Description | | ----------------- | ----------------------------------------------------------------- | | `NAME` | Route name to remove | | `--yes`, `-y` | Skip confirmation prompt | | `--config` | Path to cordon.toml (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to edit | # cordon secret Source: https://docs.codezero.io/cli/secret Manage secrets in the OS keyring. Store and manage credentials in the OS keyring for use with cordon's `keyring` secret source. Secret commands take a keyring account name directly — they are decoupled from routes and config files. Use `cordon route show ` or `cordon listener show ` to find the keyring account for a route or listener. No `--config` or `--scope` flags — secret commands operate on the OS keyring directly by account name, independent of any config file. ## cordon secret set Store a secret in the OS keyring. ```bash theme={null} cordon secret set ACCOUNT [--yes] ``` The keyring service name is always `cordon`. Prompts for the secret value interactively (the value is not echoed to the terminal). If an entry already exists for the account, prompts for confirmation before overwriting. | Argument / Option | Description | | ----------------- | ---------------------------------------------- | | `ACCOUNT` | Keyring account name | | `--yes`, `-y` | Skip confirmation prompts (overwrite existing) | ### Example ```bash theme={null} # Find the keyring account for a route: cordon route show stripe # → account: stripe-api-key # Store the credential: cordon secret set stripe-api-key # Enter secret value: **** ``` ### Piped input `cordon secret set` also accepts piped stdin for automation. The secret source should be a secret manager or ephemeral credential — never `echo`, `printf`, or a plaintext file: ```bash theme={null} # Good — secret comes from 1Password CLI, never touches disk or shell history op read "op://Engineering/Stripe API Key/secret_key" | cordon secret set stripe-api-key --yes # Bad — secret is in shell history echo 'sk_live_...' | cordon secret set stripe-api-key --yes # Bad — secret is in a plaintext file on disk cat secret.txt | cordon secret set stripe-api-key --yes ``` **Avoid passing secrets through `echo`, `printf`, or files.** These expose the credential in shell history (`~/.zsh_history`, `~/.bash_history`) or leave it as plaintext on disk. Use the interactive prompt (the default) or pipe from a secret manager. On macOS, use `cordon secret set` rather than the `security` CLI to store credentials. Cordon needs to own the keychain entry to read it without triggering an authorization dialog on every request. See [Secret Sources](/configuration/secret-sources#platform-differences) for details. **macOS SSH / headless sessions:** Keychain may reject writes with `Platform secure storage failure: User interaction is not allowed.` See [Secret Sources: macOS troubleshooting](/configuration/secret-sources#troubleshooting-macos) for the unlock steps. ## cordon secret delete Remove a secret from the OS keyring. Prompts for confirmation before deleting. ```bash theme={null} cordon secret delete ACCOUNT [--yes] ``` | Argument / Option | Description | | ----------------- | ------------------------ | | `ACCOUNT` | Keyring account name | | `--yes`, `-y` | Skip confirmation prompt | ```bash theme={null} # Delete a keyring entry cordon secret delete stripe-api-key # Skip confirmation cordon secret delete stripe-api-key --yes ``` # cordon service Source: https://docs.codezero.io/cli/service Install and manage cordon as a background service. Install cordon as an OS-managed background service that starts automatically and restarts on failure. ## cordon service install ```bash theme={null} cordon service install [NAME] [OPTIONS] ``` | Argument/Option | Default | Description | | ---------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------ | | `[NAME]` | project scope service name (`-`) | Service instance name. Mutually exclusive with `--scope`; may be combined with `--config`. | | `--config`, `-c` | scope-resolved config path | Path to an **existing** config file | | `--scope` | project | Scope used to derive service name and config path | | `--dry-run` | false | Print the resolved install plan without writing service files | This command **does not create or edit** `cordon.toml` — it only installs OS service metadata so the service runs `cordon start` with the config path you pass. The file must already exist, usually from [`cordon setup`](/cli/setup). **Services are optional.** Most development workflows don't need a background service — just run `cordon start` alongside your app (e.g., in a Procfile). Use `cordon service install` when you want the proxy to start automatically on login and restart on failure for a specific project. Each service is tied to one project's `cordon.toml`. Installs a launchd user agent at `~/Library/LaunchAgents/`. ```bash theme={null} cordon service install --config /path/to/cordon.toml ``` Installs a systemd user service at `~/.config/systemd/user/`. ```bash theme={null} cordon service install --config /path/to/cordon.toml ``` ## cordon service uninstall ```bash theme={null} cordon service uninstall [NAME] [--config PATH | --scope project|user] ``` | Argument / Option | Default | Description | | ----------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `[NAME]` | project scope service name | Service instance name (mutually exclusive with `--scope` and `--config`) | | `--config`, `-c` | — | Find the installed service whose embedded config path matches; falls back to the config-derived service name if no installed service matches | | `--scope` | project | Scope used to derive service name | ## cordon service start Start an installed cordon service. ```bash theme={null} cordon service start [NAME] [--config PATH | --scope project|user] ``` | Argument / Option | Default | Description | | ----------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `[NAME]` | auto-detected | Service instance name (mutually exclusive with `--scope` and `--config`) | | `--config`, `-c` | — | Find the installed service whose embedded config path matches; falls back to the config-derived service name if no installed service matches | | `--scope` | project | Scope used to derive service name | With no arguments, the target is auto-detected: the current directory's project service if installed, otherwise the sole installed service. If multiple services are installed and none matches the current directory, the command errors and lists them. The service must already be installed via `cordon service install`. If the service is already running, prints a warning and exits successfully. ## cordon service stop Stop a running cordon service. ```bash theme={null} cordon service stop [NAME] [--config PATH | --scope project|user] ``` | Argument / Option | Default | Description | | ----------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `[NAME]` | auto-detected | Service instance name (mutually exclusive with `--scope` and `--config`) | | `--config`, `-c` | — | Find the installed service whose embedded config path matches; falls back to the config-derived service name if no installed service matches | | `--scope` | project | Scope used to derive service name | With no arguments, the target is auto-detected: the current directory's project service if installed, otherwise the sole installed service. If multiple services are installed and none matches the current directory, the command errors and lists them. ## cordon service log View logs for a cordon service. ```bash theme={null} cordon service log [NAME] [OPTIONS] ``` | Argument / Option | Default | Description | | ----------------- | ------------- | --------------------------------------------------------- | | `[NAME]` | auto-detected | Service instance name (mutually exclusive with `--scope`) | | `--follow`, `-f` | false | Stream logs continuously (Ctrl+C to stop) | | `--tail`, `-n` | 100 | Number of recent log lines to show | | `--scope` | project | Scope used to derive service name | With no arguments, the target is auto-detected: the current directory's project service if installed, otherwise the sole installed service. If multiple services are installed and none matches the current directory, the command errors and lists them. Tails `cordon.stderr.log` under `~/Library/Logs/cordon-/` — `cordon--` for project scope, `cordon-user` for user scope. Cordon writes startup errors and diagnostics to stderr. The launchd service may also create a sibling `cordon.stdout.log`, but `cordon service log` does not read it. ```bash theme={null} cordon service log --follow ``` Reads from `journalctl --user` for the systemd service unit. ```bash theme={null} cordon service log --follow ``` ## cordon service cleanup List installed services whose embedded `--config` path no longer exists on disk (orphaned services) and remove them. Services accumulate when project directories are deleted or moved — cleanup prunes stale service definitions. ```bash theme={null} cordon service cleanup [NAME] [--dry-run | --all] ``` | Argument / Option | Description | | ----------------- | -------------------------------------------------------------------------- | | `[NAME]` | Target a single orphaned service by name. Mutually exclusive with `--all`. | | `--dry-run` | List orphaned services without prompting or removing anything | | `--all` | Remove every orphaned service without prompting (skips running ones) | `--dry-run` and `--all` are mutually exclusive. ### Interactive mode With no flags, each orphan is listed and you're prompted per service: ``` Remove 'cordon-a1b2c3d4'? [y/N/a/q] ``` * **y** — remove this service * **n** (or blank) — keep it * **a** — remove this and all remaining orphans * **q** — quit and leave remaining orphans untouched ### Running orphans If an orphaned service is still running (it was started before its config was deleted), cleanup skips it and prints a hint: ``` ⚠ Service 'cordon-deadbeef': running with missing config. Stop it first with: cordon service stop cordon-deadbeef ``` Stop the service manually, then re-run cleanup. ### Exit status Cleanup exits non-zero if any removal failed. ## Multiple instances Run separate cordon instances with different configurations: ```bash theme={null} cordon service install api-proxy --config ~/configs/api-cordon.toml cordon service install db-proxy --config ~/configs/db-cordon.toml ``` Each instance gets its own service with an independent lifecycle. # cordon setup Source: https://docs.codezero.io/cli/setup Interactive setup — generates certificates, creates config, and configures integrations. Interactive setup wizard that detects your project type, generates CA certificates, creates a `cordon.toml` config file, and configures supported integrations. Prefer `cordon setup` over creating `cordon.toml` by hand; setup allocates ports, writes absolute TLS paths, and applies safer defaults consistently. ## Usage ```bash theme={null} cordon setup [OPTIONS] cordon setup [OPTIONS] ``` ## Options | Option | Description | | ----------------- | --------------------------------------------------------------------------- | | `--config`, `-c` | Path to `cordon.toml` (default: `./cordon.toml`) | | `--yes` | Skip confirmation prompts (useful for automation and non-interactive setup) | | `--regenerate-ca` | Regenerate the CA certificate | | `--trust` | Add CA to system trust store | | `--no-trust` | Skip trust store prompt | Bare `cordon setup` is project-scope only. Claude Code and Codex add their own `--scope` option; Hermes and OpenClaw use integration-specific config paths by default. ## Examples ```bash theme={null} # Interactive setup cordon setup # Non-interactive setup with trust cordon setup --yes --trust ``` ## Project setup and services Cordon is project-first. By default, each project has its own `cordon.toml` with its own routes and credentials. A [user scope](/configuration/overview#scopes) is also available for tools that operate across projects. `cordon setup` writes `./cordon.toml` in the current directory by default, or to the path specified by `--config`. Certificates are stored outside the project tree; see [Scopes](/configuration/overview#scopes) for exact paths. Setup does not install a background service automatically. In interactive mode it may offer to install one; `--yes` suppresses the prompt and skips service install. To run cordon as a launchd/systemd service for a project, install the service explicitly after setup: ```bash theme={null} cordon service install my-project --config ./cordon.toml ``` See [process management](/guides/process-management) for more on running cordon as a service. ## Integration subcommands Integrations configure cordon for Claude Code, Codex, Hermes, or OpenClaw — setting up the proxy env vars and CA trust settings each integration needs. Each integration knows where that application stores configuration (for example, Claude Code settings, project-local or user Codex config, Hermes `.env`, or OpenClaw's `openclaw config` + daemon service env) and handles setup and teardown automatically. Currently supported: `claude-code`, `codex`, `hermes`, `openclaw`. For Codex, Cordon supports API-key authentication only. ChatGPT/OAuth-based Codex sessions may still route through the proxy transport, but Cordon does not replace or manage Codex's ChatGPT auth state. ### `cordon setup claude-code` Configure cordon for [Claude Code](/guides/claude-code). Runs the [base setup](#what-setup-does) automatically, then writes the standard proxy and CA env vars to Claude Code settings. ```bash theme={null} cordon setup claude-code ``` To manage the integration later, see [`cordon integration`](/cli/integration). | Option | Description | | ----------------- | --------------------------------------------------------------------------- | | `--config`, `-c` | Path to `cordon.toml` (defaults to selected scope's config path) | | `--yes` | Skip confirmation prompts (useful for automation and non-interactive setup) | | `--trust` | Add the CA cert to the OS trust store | | `--no-trust` | Skip trust store prompt | | `--regenerate-ca` | Regenerate the CA certificate | | `--service` | Install cordon as a background service at the end of setup | | `--no-service` | Skip the end-of-setup service install prompt | | `--scope` | `project` (default) or `user` | `--service` and `--no-service` are mutually exclusive. ### `cordon setup codex` Configure cordon for [OpenAI Codex](/guides/codex). Runs the [base setup](#what-setup-does) automatically, then writes the standard proxy and CA env vars to Codex's `.env` and `shell_environment_policy.set` entries in Codex's `config.toml`. ```bash theme={null} cordon setup codex ``` To manage the integration later, see [`cordon integration`](/cli/integration). | Option | Description | | ----------------- | --------------------------------------------------------------------------- | | `--config`, `-c` | Path to `cordon.toml` (defaults to selected scope's config path) | | `--yes` | Skip confirmation prompts (useful for automation and non-interactive setup) | | `--trust` | Add the CA cert to the OS trust store | | `--no-trust` | Skip trust store prompt | | `--regenerate-ca` | Regenerate the CA certificate | | `--service` | Install cordon as a background service at the end of setup | | `--no-service` | Skip the end-of-setup service install prompt | | `--scope` | `project` (default) or `user` | `--service` and `--no-service` are mutually exclusive. Codex filters out `CODEX_*` prefixed variables from its `.env` file, so the setup uses `SSL_CERT_FILE` instead of `CODEX_CA_CERTIFICATE`. The `CODEX_HOME` env var can override the default `~/.codex/` path. For project scope, set `CODEX_HOME="$PWD/.codex"` if you want Codex to load the project-local `.env` for its own startup-time network traffic. When you want Codex to run outside its command sandbox, launch it with: ```bash theme={null} export CODEX_HOME="$PWD/.codex" codex --dangerously-bypass-approvals-and-sandbox ``` Running Codex with `--dangerously-bypass-approvals-and-sandbox` is inherently riskier because model-generated commands run without Codex's normal sandbox boundary. We are working on a path that preserves credential protection while supporting fully sandboxed Codex sessions. ### `cordon setup hermes` Configure cordon for [Hermes Agent](/guides/hermes). Runs the [base setup](#what-setup-does) automatically with `~/.hermes/cordon.toml` by default, then writes the standard proxy and CA env vars to Hermes's default `~/.hermes/.env`. ```bash theme={null} cordon setup hermes ``` To manage the integration later, see [`cordon integration`](/cli/integration). | Option | Description | | ----------------- | --------------------------------------------------------------------------------------- | | `--config`, `-c` | Path to `cordon.toml` (default: `~/.hermes/cordon.toml`; user-scope config is rejected) | | `--yes` | Skip confirmation prompts (useful for automation and non-interactive setup) | | `--trust` | Add the CA cert to the OS trust store | | `--no-trust` | Skip trust store prompt | | `--regenerate-ca` | Regenerate the CA certificate | | `--service` | Install cordon as a background service at the end of setup | | `--no-service` | Skip the end-of-setup service install prompt | `--service` and `--no-service` are mutually exclusive. Hermes setup only supports the default `~/.hermes/` profile in v1. If `HERMES_HOME` points elsewhere, setup exits and asks you to unset it or configure Hermes manually. ### `cordon setup openclaw` Configure cordon for [OpenClaw](/guides/openclaw). Runs the [base setup](#what-setup-does) automatically with `~/.openclaw/cordon.toml` by default, then updates the default `~/.openclaw/openclaw.json` proxy settings directly and reinstalls the OpenClaw daemon (`openclaw daemon uninstall && openclaw daemon install`) with `NODE_EXTRA_CA_CERTS` exported so the gateway trusts cordon's CA. ```bash theme={null} cordon setup openclaw ``` To manage the integration later, see [`cordon integration`](/cli/integration). | Option | Description | | ----------------- | ----------------------------------------------------------------------------------------- | | `--config`, `-c` | Path to `cordon.toml` (default: `~/.openclaw/cordon.toml`; user-scope config is rejected) | | `--yes` | Skip confirmation prompts (useful for automation and non-interactive setup) | | `--trust` | Add the CA cert to the OS trust store | | `--no-trust` | Skip trust store prompt | | `--regenerate-ca` | Regenerate the CA certificate | | `--service` | Install cordon as a background service at the end of setup | | `--no-service` | Skip the end-of-setup service install prompt | `--service` and `--no-service` are mutually exclusive. OpenClaw setup only supports the default `~/.openclaw/` profile in v1. If `OPENCLAW_CONFIG_PATH` or `OPENCLAW_STATE_DIR` points elsewhere, setup exits and asks you to unset it or configure OpenClaw manually. Older OpenClaw versions failed with a CDP 502 when using `openclaw browser start` with `proxy.enabled=true`. This was fixed upstream — run `openclaw upgrade` to get the fix. See [Browser tool](/guides/openclaw#browser-tool) for details. ## What setup does Running `cordon setup` without an integration performs the base setup only: 1. Checks platform dependencies — verifies that secret providers can function at runtime (e.g., D-Bus session on Linux for keyring, 1Password CLI sign-in status). Issues are reported as warnings but do not block setup. 2. Detects the project type and language ecosystem 3. Allocates a free listen port and writes it to `cordon.toml` 4. Generates CA certificates at `~/.config/cordon/projects//certs/` 5. Detects available secret providers and adds them to the config (OS Keyring, 1Password CLI) 6. Creates `cordon.toml` with absolute cert paths 7. Generates a default proxy token and creates `tokens.toml` for the scope 8. Prints follow-up guidance for proxy env vars and CA trust If you're using Claude Code, Codex, Hermes, or OpenClaw, use the [integration subcommands](#integration-subcommands) instead — they run the base setup automatically and then configure the tool's proxy settings in one step. Use [`cordon env`](/cli/env) for the generated proxy and CA variables, [TLS](/configuration/tls) for trust behavior, and [SDK Compatibility](/guides/sdk-compatibility#runtime-ca-and-proxy-notes) for language-specific runtime notes. ## Certificate storage Certificates are stored outside your project directory, so they are not at risk of being committed to git with your app code. See [Scopes](/configuration/overview#scopes) for the canonical path table and [TLS](/configuration/tls#security-considerations) for CA private-key handling. ## Multiple projects Each project gets its own `cordon.toml` with its own listen port, routes, and certificate namespace. Setup allocates a free port automatically; inspect the generated `listen = ...` line in each `cordon.toml` if you need the exact port. Certificate namespaces are derived from the project directory path, so two projects with the same directory name in different locations get separate cert stores automatically. If you install background services for multiple projects, use distinct names: ```bash theme={null} cd ~/project-a && cordon service install project-a --config ./cordon.toml cd ~/project-b && cordon service install project-b --config ./cordon.toml ``` ## Removal recipes To disable an integration, see [`cordon integration disable`](/cli/integration#cordon-integration-disable). To remove cordon itself from a machine after all integrations are disabled: ```bash theme={null} # Remove CA from the system trust store, if present cordon untrust --config /path/to/cordon.toml # Remove any installed services cordon service uninstall my-project cordon service uninstall # uninstalls the current project's service # Delete cordon-managed data rm -rf ~/.config/cordon/ ``` If you've already deleted `cordon.toml` and the cert files, `cordon untrust` won't be able to locate the CA to remove. In that case, remove it manually — on macOS, open Keychain Access and search for "cordon"; on Linux, remove the cert from `/usr/local/share/ca-certificates/` and run `update-ca-certificates`. # cordon start Source: https://docs.codezero.io/cli/start Start the proxy server. Start the cordon proxy server. The proxy loads config, resolves secrets, and begins intercepting requests. ## Usage ```bash theme={null} cordon start [OPTIONS] ``` ## Options | Option | Default | Description | | ---------------- | ------- | ------------------------------------------------------- | | `--config`, `-c` | — | Path to config file (mutually exclusive with `--scope`) | | `--scope` | project | `project` or `user` — selects which config file to load | | `--log-level` | `info` | Log level: `trace`, `debug`, `info`, `warn`, `error` | `--config` and `--scope` are mutually exclusive — passing both is an error. Omitting both defaults to project scope (`$CWD/cordon.toml`). ## Examples ```bash theme={null} # Start with project-scope config (./cordon.toml) cordon start # Start with user-scope config (~/.config/cordon/cordon.toml) cordon start --scope user # Start with a custom config file cordon start --config /path/to/cordon.toml # Start with debug logging cordon start --log-level debug ``` ## Behavior 1. Parses and validates the config file. Exits with an error if invalid. Relative `ca_cert_path` / `ca_key_path` values are resolved against the config file's directory, so configs work correctly when the working directory differs (e.g., when running as a launchd/systemd service). 2. Validates all HTTP route secrets from configured sources. If multiple secrets fail, all failures are reported at once so you can fix them in a single pass. 3. Loads TLS certificates when needed for HTTP MITM or PostgreSQL client TLS. 4. Resolves PostgreSQL listener credentials at startup (when PostgreSQL listeners are configured). 5. Binds the listener on `127.0.0.1:`. The health endpoint serves `200` from this point. 6. Begins accepting connections. Response bodies are streamed through without buffering, so SSE and chunked transfer encoding responses (e.g., streaming AI API calls) work correctly. The proxy runs in the foreground. Use `Ctrl+C` to stop, or run as a [background service](/guides/process-management). ## Update checks On every start, Cordon sends a background HTTPS request to check for newer releases. The first start creates an opaque install ID and sends an `install` event; subsequent starts send a `start` event. The response is cached so that other commands can display the notice without an additional network call. When the cache indicates a newer version is available, every command (except `upgrade`, `help`, `env`, and `status --quiet`) will show an update notice on stderr. In interactive sessions (stdin and stderr are terminals), Cordon prompts to upgrade in place: ``` Update available: v0.4.0 (current: v0.3.1) Upgrade now? [Y/n] ``` Accepting runs the appropriate package manager (`brew upgrade cordon` or `npm install -g @codezero-io/cordon@latest`), verifies the result, and re-executes the original command on the new binary. Declining or pressing Enter in non-interactive sessions proceeds normally. You can also upgrade explicitly at any time with [`cordon upgrade`](/cli/upgrade). See [Telemetry and update checks](/security/overview#telemetry-and-update-checks) for the full list of data sent and opt-out options. # cordon status Source: https://docs.codezero.io/cli/status Show running cordon instances and their health. Display the status of cordon instances — running processes, listening ports, and service health. ## Usage ```bash theme={null} cordon status [OPTIONS] [NAME] cordon status list ``` | Argument / Option | Description | | ----------------- | --------------------------------------------------------------------------------------------------- | | `[NAME]` | Named installed service. Looks up that service file to determine config path and status. | | `list` | List all installed services and standalone cordon processes. | | `--config`, `-c` | Check the instance using this config file. Mutually exclusive with `--scope`. | | `--scope` | `project` or `user`; selects which config file to inspect. | | `--verbose`, `-v` | Show detailed instance information. | | `--quiet`, `-q` | Suppress output and use exit code only. Cannot be combined with names, subcommands, or `--verbose`. | Shows information about: * Running cordon processes * Listening ports * Installed service status * Health endpoint responses * Token enforcement status and warnings (missing tokens, unenforced scopes) When `./cordon.toml` is missing, `cordon status` falls back to installed services: * If exactly one installed service exists, it inspects that service automatically * If multiple installed services exist, it shows the service list and asks you to pick one by name ## Quiet mode ```bash theme={null} cordon status -q cordon status --quiet ``` Suppresses all output and communicates via exit code only: | Exit code | Meaning | | --------- | -------------------------------------------------------------- | | 0 | Cordon is running and healthy | | 1 | Cordon is not responding, or config exists but can't be parsed | If no config file is found (cordon not configured for this project/scope), exits 0 — nothing to check. Quiet mode performs a fast health check: 50ms timeout with one retry (20ms delay between attempts). It skips service discovery and process scanning — just loads the config, reads the port, and hits the `/health` endpoint. This is used by the [Claude Code health-check hook](/guides/claude-code#health-check-hook) to detect when cordon is down. It's also useful in scripts, CI, and shell prompts: ```bash theme={null} cordon status -q || echo "cordon is down" ``` ## Examples ```bash theme={null} # Check current project config cordon status # Check user-scope config cordon status --scope user # Check a named installed service cordon status user # List every service/process Cordon can find cordon status list # Show detailed health, PID, routes, and service metadata cordon status --verbose ``` # cordon stop Source: https://docs.codezero.io/cli/stop Stop a running cordon proxy. Stop a running cordon proxy. Finds the process listening on the configured port and sends SIGTERM. ## Usage ```bash theme={null} cordon stop [OPTIONS] ``` ## Options | Option | Description | | ---------------- | ----------------------------------------------------------------- | | `--config`, `-c` | Path to config file (mutually exclusive with `--scope`) | | `--scope` | `project` (default) or `user` — selects which config file to read | `--config` and `--scope` are mutually exclusive — passing both is an error. Omitting both defaults to project scope (`$CWD/cordon.toml`). ## Examples ```bash theme={null} # Stop the project-scope proxy cordon stop # Stop the user-scope proxy cordon stop --scope user # Stop a proxy using a specific config file cordon stop --config /path/to/cordon.toml ``` ## Behavior 1. Reads the config file to determine the listen port. 2. Checks if a process is listening on that port. If not, prints a warning and exits. 3. Checks if the port is managed by an installed service. If so, refuses to stop and suggests using `cordon service stop` instead. 4. Verifies the process on the port is actually cordon before sending SIGTERM. If cordon is running as an installed service, use `cordon service stop` instead — `cordon stop` will detect the service and point you there. # cordon token Source: https://docs.codezero.io/cli/token Manage proxy authentication tokens. Manage the default and client tokens stored in `tokens.toml`. Tokens authenticate clients to the local proxy for configured route-host traffic. Full route matches still decide whether credentials are injected. Token values are never printed by `cordon token`; use [`cordon env`](/cli/env) for generic clients. The **default token** is the general-purpose proxy credential for a Cordon instance. **Client tokens** are named proxy credentials managed for configured clients such as Claude Code, Codex, Hermes, and OpenClaw. In the current token model, all valid tokens grant the same proxy access; client tokens exist for independent rotation, revocation, setup/remove cleanup, and logging. Workload identity is verified separately. When enforcement is active, unmatched forwarding still works without a token. If a request does include `Proxy-Authorization`, Cordon rejects malformed, duplicate, or invalid values before forwarding. ## Usage ```bash theme={null} cordon token [OPTIONS] ``` All commands accept one location selector: | Option | Description | | ---------------- | -------------------------------------------------------- | | `--scope` | Token scope: `project` (default) or `user` | | `--config`, `-c` | Resolve token storage from a specific `cordon.toml` path | For Hermes and OpenClaw client tokens, omit `--scope` to use the integration's default config path (or a legacy user-scope config during migration), or pass `--config` explicitly. `--scope project` is rejected for these global app integrations because it would otherwise target the caller's current directory. ## Commands ### `cordon token list` List configured token names and creation times without printing token values. ```bash theme={null} cordon token list --scope project ``` ### `cordon token rotate` Rotate one token or every configured token. Omitting the token name rotates the default token. ```bash theme={null} cordon token rotate cordon token rotate default cordon token rotate codex --scope user cordon token rotate hermes cordon token rotate --all ``` When the token belongs to a supported client (`claude-code`, `codex`, `hermes`, or `openclaw`), rotation updates that client's tokenized proxy URL before writing the new token file. Restart any running client process that already inherited the old URL. ### `cordon token revoke` Revoke a client token. ```bash theme={null} cordon token revoke claude-code ``` The default token cannot be revoked; rotate it instead. For supported clients, revoke removes matching tokenized proxy settings from the client config after removing the token from `tokens.toml`. ## Token storage | Scope | Token file | | ------- | --------------------------------------------------------- | | Project | `~/.config/cordon/projects/-/tokens.toml` | | User | `~/.config/cordon/user/tokens.toml` | On Unix, Cordon writes `tokens.toml` with `0600` permissions and refuses symlinked, wrong-owner, or group/world-readable token files. ## See also * [Token migration guide](/guides/token-migration) * [`cordon env`](/cli/env) * [`cordon setup`](/cli/setup) # cordon trust / untrust Source: https://docs.codezero.io/cli/trust Manage the CA certificate in the system trust store. ## cordon trust Add the proxy CA certificate to the system trust store so tools accept the proxy's TLS certificates. ```bash theme={null} cordon trust [OPTIONS] ``` | Option | Default | Description | | ---------------- | -------------------------- | ------------------------------------------------------------------------------------------- | | `--config`, `-c` | scope-resolved config path | Path to config file (to locate the CA cert). Mutually exclusive with `--scope`. | | `--scope` | project | `project` or `user`; selects which config file to read. Mutually exclusive with `--config`. | After running `cordon trust`, tools that read the system trust store will trust the proxy automatically. Some runtimes do not use the system trust store. Use [`cordon env`](/cli/env) for app-specific CA variables and [SDK Compatibility](/guides/sdk-compatibility#runtime-ca-and-proxy-notes) for runtime-specific behavior. ## cordon untrust Remove the proxy CA certificate from the system trust store. ```bash theme={null} cordon untrust [OPTIONS] ``` | Option | Default | Description | | ---------------- | -------------------------- | ------------------------------------------------------------------------------------------- | | `--config`, `-c` | scope-resolved config path | Path to config file. Mutually exclusive with `--scope`. | | `--scope` | project | `project` or `user`; selects which config file to read. Mutually exclusive with `--config`. | # cordon upgrade Source: https://docs.codezero.io/cli/upgrade Upgrade cordon to the latest version. Upgrade cordon to the latest version using the detected package manager. ## Usage ```bash theme={null} cordon upgrade ``` ## Behavior 1. Checks for the latest version via the update API (always performs a network call, even if `CORDON_NO_UPDATE_CHECK` is set). 2. If already on the latest version, prints a message and exits. 3. Detects the install method from the executable path: * **Homebrew** — binary under `/opt/homebrew/`, `/usr/local/Cellar/`, or `/home/linuxbrew/.linuxbrew/` * **npm** — binary under a `node_modules` directory or npm's global prefix * **Unknown** — prints a link to the GitHub releases page and exits with an error 4. Runs the appropriate upgrade command (`brew upgrade cordon` or `npm install -g @codezero-io/cordon@latest`). 5. Verifies the upgraded binary reports the expected version. ## Interactive prompt You don't need to run `cordon upgrade` explicitly. When a newer version is available and the session is interactive (stdin and stderr are terminals), most commands will prompt before running: ``` Update available: v0.4.0 (current: v0.3.1) Upgrade now? [Y/n] ``` Accepting upgrades and re-executes the original command on the new binary. The prompt is skipped for `upgrade`, `help`, `env`, and `status --quiet`. In non-interactive sessions (piped input, CI, scripts), a one-line notice is printed to stderr with no prompt. ## Running services `cordon upgrade` replaces the binary on disk but does not restart running services. A launchd or systemd service continues executing the old binary in memory until restarted: ```bash theme={null} cordon upgrade cordon service stop && cordon service start ``` If you use an integration that maintains persistent connections to cordon (Claude Code, Codex, Hermes, OpenClaw), restart those sessions after restarting the service — they hold connections to the previous cordon process and will get stale-connection errors (typically TLS certificate failures) until reconnected. For OpenClaw, restart the gateway daemon (`openclaw daemon restart`). ## Examples ```bash theme={null} # Upgrade to latest cordon upgrade # Non-interactive notice (no prompt) cordon status | jq . # stderr: Update available: v0.4.0 (current: v0.3.1) ``` # cordon wait Source: https://docs.codezero.io/cli/wait Wait for the proxy to be ready before starting your application. Polls the health endpoint until the proxy reports ready. Useful in Procfiles, scripts, and CI pipelines to ensure the proxy is accepting connections before starting dependent applications. ## Usage ```bash theme={null} cordon wait [OPTIONS] ``` ## Options | Option | Default | Description | | ---------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `--url` | derived from config, else `http://127.0.0.1:6790/health` | Health check URL. Overrides and conflicts with `--config` and `--scope`. | | `--config`, `-c` | — | Read `listen` from this config file. Mutually exclusive with `--url` and `--scope`. | | `--scope` | project | Read `listen` from the selected scope's config. Mutually exclusive with `--url` and `--config`. | | `--timeout` | `30` | Timeout in seconds | Without `--url`, `cordon wait` uses the selected config's `listen` port. With no flags, it tries project scope first and falls back to `http://127.0.0.1:6790/health` if no project config exists. ## Examples ```bash theme={null} # Wait with defaults cordon wait # Wait with custom timeout cordon wait --timeout 60 # Wait on the user-scope proxy cordon wait --scope user # Wait on an explicit health URL cordon wait --url http://127.0.0.1:7000/health # Use in a Procfile # web: cordon wait && npm run dev ``` ## Exit codes | Code | Meaning | | ---- | -------------------------------------------- | | `0` | Proxy is ready | | `1` | Timeout reached — proxy did not become ready | # Listeners Source: https://docs.codezero.io/configuration/listeners Configure PostgreSQL listener ports and database credential injection. For database connections, cordon can inject credentials at the wire protocol level. PostgreSQL listeners are configured as `[[listeners]]` entries, separate from HTTP [routes](/configuration/routes), with `port` for the local listener and `upstream` for the real database host and port. Prefer [`cordon listener add`](/cli/listener#cordon-listener-add) and [`cordon listener edit`](/cli/listener#cordon-listener-edit) over hand-editing listener entries in `cordon.toml`. The CLI validates listener names, ports, TLS mode, and secret-source fields, and is meant as the safer way to produce this configuration. `upstream` is taken as-is and not format-checked at `add`/`edit` time — a malformed address (e.g. missing port) is only caught when `cordon start` tries to parse it. The TOML below is the reference format when you need to inspect, review, or automate outside the CLI. ## Listener structure ```toml theme={null} [[listeners]] name = "prod-db" port = 15432 upstream = "db.prod.example.com:5432" client_tls = "accept" [listeners.auth] type = "password" username = "app_user" [listeners.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Postgres Prod" field = "password" ``` | Field | Type | Required | Description | | --------------- | ------- | -------- | --------------------------------------------------------------------------------------------- | | `name` | string | Yes | Identifier for the listener. Route and listener names share one namespace and must be unique. | | `port` | integer | Yes | Local port to listen on. | | `upstream` | string | Yes | Upstream database host and port, such as `db.example.com:5432`. | | `client_tls` | string | No | Client-to-Cordon TLS mode: `accept` (default), `require`, or `disable`. | | `auth.type` | string | Yes | Auth type. Currently `password`. | | `auth.username` | string | Yes | Username to authenticate as. | | `auth.secret` | object | Yes | Secret source reference. See [Secret Sources](/configuration/secret-sources). | Connect your application to `localhost:15432` instead of the upstream database. Cordon injects the password during the PostgreSQL authentication handshake. Listener ports must be unique and cannot equal the HTTP proxy `listen` port. ## TLS behavior `client_tls = "accept"` accepts PostgreSQL SSL requests when valid `tls.ca_cert_path` and `tls.ca_key_path` are configured, while still allowing plaintext clients. `client_tls = "require"` rejects plaintext startup packets and requires those CA paths. `client_tls = "disable"` declines SSL requests and keeps plaintext-only behavior. Omitted `client_tls` behaves as `accept`. PostgreSQL client TLS uses the configured Cordon CA. Clients using `sslmode=verify-ca` or `sslmode=verify-full` must trust that CA. Cordon presents a loopback certificate for `localhost`, `127.0.0.1`, and `::1`; for `sslmode=verify-full`, connect to the local listener with one of those host identities. Cordon always requires TLS on the Cordon-to-upstream PostgreSQL leg. It sends a PostgreSQL `SSLRequest` to the upstream server and completes TLS before sending startup or authentication data. If the upstream declines TLS or certificate verification fails, Cordon fails closed and does not send credentials. ## Upstream policy PostgreSQL listener upstreams are configured trust decisions in v1, matching configured HTTP credential routes. Cordon resolves the upstream through the central resolver and connects to the selected `SocketAddr`, so DNS is pinned before the listener starts forwarding connections. Private, loopback, and link-local upstream policy will be revisited post-v1 in [#531](https://github.com/codezero-io/cordon/issues/531). # Configuration Overview Source: https://docs.codezero.io/configuration/overview The cordon.toml config file controls which routes are proxied and how credentials are injected. Cordon is configured via **`cordon.toml`**. Prefer the CLI over hand-editing this file: `cordon setup` creates it, and `cordon route` / `cordon listener` commands validate changes and write the safer defaults for you. The file format is documented here for review, troubleshooting, and automation when a CLI command does not cover your use case. See [setup](/cli/setup#project-setup-and-services) for details. ## Scopes Cordon is project-first — most integrations default to project scope, giving each repository its own config, credentials, and proxy instance. A **user** scope is also available for tools that span projects. The two scopes are independent: each runs its own daemon with its own config file, TLS certificates, and optional OS service. They are not merged or layered. | Resource | Project scope | User scope | | ---------------- | ----------------------------------------------------------- | ------------------------------------- | | Config file | `$CWD/cordon.toml` | `$XDG_CONFIG_HOME/cordon/cordon.toml` | | TLS certificates | `$XDG_CONFIG_HOME/cordon/projects/-/certs/` | `$XDG_CONFIG_HOME/cordon/user/certs/` | | Service name | `-` | `user` | `-` is the project directory basename plus the first 8 hex characters of the SHA-256 of the absolute CWD path, so two projects with the same directory name never collide. On macOS (launchd) and Linux (systemd user units) these names are automatically namespaced. The launchd label becomes `io.codezero.cordon.`. ### Directory layout Cordon follows the [XDG Base Directory spec](https://specifications.freedesktop.org/basedir-spec/latest/) for its config location. `$XDG_CONFIG_HOME` defaults to `~/.config` on both Linux and macOS — cordon intentionally uses `~/.config/cordon/` on macOS for consistency with cross-platform dev tools like git and VS Code. Set `XDG_CONFIG_HOME=/custom/path` to override; cordon will then use `/custom/path/cordon/`. ``` ~/.config/cordon/ # base directory ├── cordon.toml # user-scope config ├── user/ │ └── certs/ # user-scope TLS material │ ├── ca-cert.pem │ └── ca-key.pem └── projects/ └── myapp-a1b2c3d4/ # - └── certs/ # project-scope TLS material ├── ca-cert.pem └── ca-key.pem / └── cordon.toml # project-scope config (one per project) ``` Project-scope configs live beside your code. Everything else — user-scope config and all TLS certificates — lives under the user's cordon config directory. The `-` naming uses SHA-256 of the absolute project path so two projects sharing the same directory name never collide. ### Port allocation Each `cordon setup` invocation — project or user scope — asks the OS for a free port and writes it into `cordon.toml` at setup time. In practice this avoids collisions between concurrent projects and rapid successive `cordon setup` invocations. There is no base port to configure. The allocated port is not reserved: `cordon setup` binds to port 0, reads the port the OS assigned, then releases the socket before writing the number into `cordon.toml`. Between setup and `cordon start`, another process could theoretically claim the port. If that happens, `cordon start` will fail to bind and you can rerun `cordon setup` to pick a new port. Persistent port reservation and re-allocation at start time is future work. ### Per-integration defaults | Integration | Default config target | Rationale | | ------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------- | | `claude-code` | Project scope: `./cordon.toml` | Editor configs are typically per-repo | | `codex` | Project scope: `./cordon.toml` | Project-specific Codex env isolates credentials | | `hermes` | Project-style config: `~/.hermes/cordon.toml` | Isolates Hermes from unrelated repo configs while matching Hermes's default profile | | `openclaw` | Project-style config: `~/.openclaw/cordon.toml` | Isolates OpenClaw from unrelated repo configs while matching OpenClaw's default profile | ### Overriding the scope Pass `--scope user` or `--scope project` to override the default for `claude-code` or `codex`: ```bash theme={null} # Configure Claude Code globally (one config for all projects) cordon setup claude-code --scope user ``` Hermes and OpenClaw do not expose `--scope`. Their default config paths are `~/.hermes/cordon.toml` and `~/.openclaw/cordon.toml`; pass `--config` to follow-up commands when you need to target those instances explicitly. ### When to choose which scope * **Project scope** — the right default for most tools. Each repo gets its own credentials, port, and service. No cross-project interference. * **User scope** — best when you want a single cordon instance shared across repos. One cordon process handles all requests. ### `cordon start` and scope `cordon start` with no flags uses `./cordon.toml` (project scope). To start the user-scope instance explicitly: ```bash theme={null} cordon start --scope user ``` For Hermes or OpenClaw, target the integration config directly: ```bash theme={null} cordon start --config ~/.hermes/cordon.toml cordon start --config ~/.openclaw/cordon.toml ``` ### `cordon service` and scope `cordon service install/start/stop --scope ` derives the service name and config path from the scope automatically: ```bash theme={null} cordon service install --scope project # installs - cordon service install --scope user # installs user ``` For Hermes or OpenClaw, target the integration config directly: ```bash theme={null} cordon service install --config ~/.hermes/cordon.toml cordon service install --config ~/.openclaw/cordon.toml ``` After setup, use commands like `cordon route add`, `cordon route edit`, `cordon listener add`, and `cordon listener edit` to modify credential rules. For automation, prefer non-interactive CLI flags (for example, `cordon setup --yes` or fully-flagged `cordon route add`) instead of maintaining a hand-written config. [`cordon service install`](/cli/service) does **not** write `cordon.toml` — it points an OS service at an existing file, typically the project-local file setup produced. Paths in `cordon.toml` are literal strings: Cordon does **not** expand `$HOME`, `~`, or other environment variables. Use the absolute paths written by `cordon setup`, or substitute placeholders like elsewhere in these docs (`/path/to/...`). If `ca_cert_path` or `ca_key_path` are relative, they are resolved against the config file's directory (not the working directory), so hand-edited configs work correctly under service managers where the working directory is `/`. ## Minimal example The `listen` port is assigned automatically by `cordon setup` — the `6790` shown in the example is illustrative; your actual port is OS-assigned and typically in the ephemeral range. Check the `listen = ...` line in your generated `cordon.toml` to find the port in use. ```toml theme={null} listen = 6790 # illustrative — your setup may allocate a different port [tls] enabled = true # Substitute paths from your real `cordon.toml` (typically under ~/.config/cordon/projects//certs/). ca_cert_path = "/path/to/ca-cert.pem" ca_key_path = "/path/to/ca-key.pem" [[routes]] name = "stripe" [routes.match] host = "api.stripe.com" [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` ## Top-level fields | Field | Type | Required | Description | | --------------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `listen` | integer | Yes | Port number to listen on. The proxy always binds to `127.0.0.1` (loopback) — this is not configurable because binding to a non-loopback address would expose injected credentials to the network. | | `tls` | object | No | TLS interception settings. Required only when you enable HTTPS MITM or require PostgreSQL client TLS. | | `secrets` | object | No | Secret provider configuration. Use to specify an explicit path to the `op` binary when it's not on `PATH` (common for background services). See [Secret Sources — Provider configuration](/configuration/secret-sources#provider-configuration-optional). | | `routes` | array | No | List of HTTP credential route definitions. Matched routes are explicit trust decisions; see [Routes](/configuration/routes#how-matching-works). | | `listeners` | array | No | List of PostgreSQL listener definitions. See [Listeners](/configuration/listeners). | | `denylist_exceptions` | array of strings | No | `host:port` destinations carved out of the always-on SSRF denylist, without credential injection. See [Denylist exceptions](#denylist-exceptions). | ## TLS settings | Field | Type | Required | Description | | ------------------ | ------- | ----------- | -------------------------------------------------------------------------------------------------------------------- | | `tls.enabled` | boolean | No | Enable HTTPS interception via TLS MITM. Defaults to `false` if omitted. | | `tls.ca_cert_path` | string | Conditional | Path to the CA certificate file. Required when HTTPS MITM is enabled or PostgreSQL `client_tls = "require"` is used. | | `tls.ca_key_path` | string | Conditional | Path to the CA private key file. Required when HTTPS MITM is enabled or PostgreSQL `client_tls = "require"` is used. | When TLS is enabled, cordon performs MITM on HTTPS connections to configured route hosts. Inner request path and method filters decide whether credentials are stripped and injected after the tunnel is decrypted. It generates per-host certificates signed by the local CA. See [TLS](/configuration/tls) for details. Route and listener names share one namespace and must be unique. Listener ports must also be unique and cannot equal the HTTP proxy `listen` port. ## Denylist exceptions Cordon permits outbound requests to any public destination by default — it does not maintain an egress allowlist. What it always enforces is an SSRF denylist that blocks private, loopback, link-local, and cloud metadata addresses, plus CGNAT, `0.0.0.0/8`, documentation and benchmarking ranges, multicast, IETF-reserved space, and the IPv6 equivalents of these. The `denylist_exceptions` list carves specific `host:port` destinations out of that denylist, without credential injection. This is useful for local development services (databases, mock APIs, local Supabase instances) that the proxy needs to forward traffic to but that do not require credential injection. ```toml theme={null} # top-level in cordon.toml, alongside `listen` denylist_exceptions = ["localhost:54321", "localhost:4201"] ``` `denylist_exceptions` is a top-level key. In TOML, bare keys belong to whatever table precedes them, so it must appear **before** any `[table]` header (such as `[tls]` or `[[routes]]`). Placed after a table, TOML parses it as a field of that table and cordon rejects the config with an `unknown field` error. | Field | Type | Required | Description | | --------------------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | | `denylist_exceptions` | array of strings | No | List of `host:port` pairs carved out of the SSRF denylist. Each entry must include a port — host-only entries are not supported. | **Loopback expansion:** Loopback entries auto-expand across `localhost`, `127.0.0.1`, and `[::1]`. Configuring `localhost:54321` also allows `127.0.0.1:54321` and `[::1]:54321`. **Denylist bypass scope:** Entries bypass the entire SSRF denylist, not just loopback blocking. An exception for a private-network address (e.g., `192.168.1.50:8080`) will be allowed through. **Trust model:** Denylist exceptions follow the same operator opt-in trust model as configured routes. Each entry is an explicit decision by the developer to permit a denylisted destination. Unlike routes, exceptions do not inject credentials — requests are forwarded unmodified. ## Extended examples Keep this page as the map of `cordon.toml`. Use the focused references for complete examples: * [Routes](/configuration/routes) for HTTP credential injection, auth types, provider examples, and route matching. * [Listeners](/configuration/listeners) for PostgreSQL listener configuration. * [Secret Sources](/configuration/secret-sources) for 1Password, keyring, rotation, and platform behavior. ## Config file location `cordon start` resolves the config file based on scope: * **Project scope** (default) — `./cordon.toml` * **User scope** (`--scope user`) — `$XDG_CONFIG_HOME/cordon/cordon.toml` (or `~/.config/cordon/cordon.toml` if `XDG_CONFIG_HOME` is not set) `cordon setup` writes the config to the scope-appropriate path automatically. See [Scopes](#scopes) above for the full scope path table. You can bypass scope resolution entirely with the `--config` flag: ```bash theme={null} cordon start --config /path/to/cordon.toml ``` `cordon.toml` typically contains project-specific secret references and should be gitignored. # Routes Source: https://docs.codezero.io/configuration/routes Configure which hosts are intercepted and how credentials are injected. Routes define which outbound requests cordon intercepts and what credentials to inject. Each route matches on a destination hostname and specifies an auth type. Prefer [`cordon route add`](/cli/route#cordon-route-add) and [`cordon route edit`](/cli/route#cordon-route-edit) over hand-editing routes in `cordon.toml`. The CLI validates hostnames, auth types, headers, secret-source fields, and scoped match fields such as path and method filters. The TOML below is the reference format when you need to inspect, review, or automate outside the CLI. ## Route structure ```toml theme={null} [[routes]] name = "stripe" [routes.match] host = "api.stripe.com" path_glob = "/v1/**" methods = ["POST", "PUT", "DELETE"] [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` | Field | Type | Required | Description | | ------------------ | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string | Yes | Identifier for the route (used in logs). | | `match.host` | string | Yes | Hostname to match (case-insensitive). Use `*` inside a hostname label for one-label wildcards or globs, such as `"*.stripe.com"` or `"api-*.stripe.com"`. A wildcard label must be followed by at least two literal labels — `"*.com"` and `"*.app"` are rejected. | | `match.path_exact` | string | No | Exact absolute path to match after percent-decoding and dot-segment normalization. `"/v1/comments"` matches that endpoint only. | | `match.path_glob` | string | No | Absolute path glob for variable path segments, such as `"/v1/*/comments/**"`. `*` matches exactly one path segment; `**` matches zero or more path segments. For "match everything under this subtree" use `"/v1/**"`. | | `match.methods` | array of strings | No | Uppercase HTTP methods that may receive credentials, such as `["POST", "PUT", "DELETE"]`. Omit to match all methods. | | `auth.type` | string | Yes | `header` or `basic`. | | `auth.username` | string | `basic` only | Username for HTTP Basic authentication. | | `auth.header_name` | string | `header` only | Header name for header auth. | | `auth.scheme` | string | `header` only | Optional HTTP auth scheme. Required when `auth.header_name` is `Authorization`. | | `auth.secret` | object | Yes | Secret source reference. See [Secret Sources](/configuration/secret-sources). | When `auth.secret.source` is `"1password"`, the following fields are available: | Field | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `auth.secret.source` | string | Yes | `"1password"` | | `auth.secret.account` | string | No | 1Password account identifier — sign-in address, shorthand, account UUID, or user UUID. Recommended for multi-account and service setups. See the [1Password guide](/guides/onepassword#finding-your-account-identifier). | | `auth.secret.vault` | string | Yes | Vault name within the account. | | `auth.secret.item` | string | Yes | Item name within the vault. | | `auth.secret.field` | string | Yes | Field label within the item (case-sensitive). | ## Auth types ### Header auth Injects a configured header with the secret value. Set `scheme` for `Authorization: Bearer ` style headers, or omit it for raw API-key headers. ```toml theme={null} [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` ### Basic auth Injects an `Authorization: Basic ` header. ```toml theme={null} [routes.auth] type = "basic" username = "myuser" [routes.auth.secret] source = "keyring" account = "my-basic-cred" ``` ### Raw API key header Injects a custom header with the secret value. ```toml theme={null} [routes.auth] type = "header" header_name = "X-Api-Key" [routes.auth.secret] source = "keyring" account = "example-api-key" ``` `header_name` must be a valid HTTP field name and cannot be one of Cordon's reserved credential injection headers: `Host`, `Content-Length`, `Transfer-Encoding`, `Connection`, `Keep-Alive`, `TE`, `Trailer`, `Upgrade`, `Proxy-Authorization`, or `Proxy-Authenticate`. ## How matching works * Cordon first matches on the **hostname** of the outbound request after canonicalization (case-insensitive, trailing dot ignored). * Exact host entries match one hostname only. Host entries containing `*` are globs where `*` matches zero or more characters within a single hostname label. For example, `"*.stripe.com"` matches `"api.stripe.com"` but not `"stripe.com"` or `"deep.api.stripe.com"`. * A configured host match is the operator's trust decision for credential injection, SSRF denylist exemption, and CONNECT interception. To prevent the most common footguns, cordon rejects host globs where a wildcard label is not followed by at least two literal labels: `"*.com"`, `"*.app"`, and `"foo.*"` are rejected; `"*.vercel.app"`, `"api-*.stripe.com"`, and `"*.preview.acme.com"` are accepted. This rule does not enforce [public suffix](https://publicsuffix.org/) awareness. Patterns such as `"*.vercel.app"`, `"*.github.io"`, or `"*.herokuapp.com"` pass the check but match subdomains owned by different parties — every Vercel project, GitHub user, or Heroku app under those suffixes. Use the narrowest pattern your workflow allows, such as `"-*.vercel.app"`, when you need to scope to deployments you control. * Configure at most one path matcher: `path_exact` for one endpoint, or `path_glob` for variable segments (use `"/v1/**"` for "match everything under this subtree"). * Configs written by Cordon 0.4.0 may contain `path_prefix`. Cordon still accepts that legacy field. Prefixes that can be represented exactly as globs are rewritten as `path_glob` when the config is saved; other legacy prefixes keep prefix semantics to avoid broadening credential scope. * If `path_exact` is set, the normalized request path must exactly equal that path. * If `path_glob` is set, `*` must be a complete path segment and matches exactly one non-empty path segment. `**` must also be a complete path segment and matches zero or more whole path segments. For example, `"/v1/*/comments/**"` matches `"/v1/post/comments"` and `"/v1/post/comments/123"`, but not `"/v1/comments"`, `"/v1/a/b/comments/123"`, or `"/v1/post-*/comments"`. * Encoded traversal such as `"/v1/%2e%2e/admin"` does not match scoped paths. Encoded path separators such as `"%2F"` or `"%5C"` are treated as non-matches because Cordon forwards the original URI upstream. Paths that would still contain encoded dots or path separators after one decode, such as `"/v1/%252e%252e/admin"`, are also treated as non-matches for credential injection. * Literal backslashes are also treated as non-matches for scoped paths because some upstream servers interpret them as path separators. Empty path segments are preserved, so `//v1/charges` does not match `/v1/charges`. * If `methods` is set, the request method must exactly match one of the configured uppercase method tokens. * On a full route match (host plus any configured path and method filters), cordon strips `Authorization`, `Proxy-Authorization`, and the configured credential header, then injects the managed credential. * If the host matches but path or method filters do not, the request is not Cordon-managed credential traffic. It passes through without origin-auth stripping or injection. * Unmatched requests are forwarded without modification. * Routes are evaluated in order; the first full match wins. Place more-specific routes before broad catch-all routes for the same host. The CLI appends new routes, so reorder `cordon.toml` manually if a broad route was added first. For HTTPS `CONNECT` traffic, Cordon uses the CONNECT authority to decide whether a configured host should be MITM-intercepted. Path and method filters are evaluated only after the inner HTTPS request is decrypted, so unmatched paths or methods pass through the tunnel without credential stripping or injection. Configured HTTP route hosts are explicit trust decisions. In v1, requests to a configured route host bypass private/link-local/loopback SSRF denylist checks so internal APIs, VPN/private endpoints, PrivateLink services, staging environments, and localhost development services continue to work even when a request does not satisfy optional path or method credential-injection filters. For non-credentialed access to denylist-blocked destinations, see [`denylist_exceptions`](/configuration/overview#denylist-exceptions) instead. DNS pinning still applies, but DNS pinning is not private-IP blocking. Routes authorize credential injection and upstream selection for the configured host; they do not protect against malicious same-user callers. Post-v1 private-upstream policy work will revisit this default. Auth stripping is unconditional on full route matches. If your application sends `Authorization: Bearer placeholder` to a request that satisfies host, path, and method filters, cordon removes it and injects the real token. Requests outside those filters are forwarded unchanged. Route names share one namespace with PostgreSQL listener names and must be unique. See [Listeners](/configuration/listeners) for PostgreSQL listener configuration. ## Provider auth quick reference Use the auth type expected by the upstream API, even if your app or agent only sends a dummy credential. Cordon strips any inbound credential on full route matches and injects the real value from the configured [secret source](/configuration/secret-sources). | Provider / API | Host | `header_name` | `scheme` | Notes | | -------------- | ------------------- | --------------- | -------- | ----------------------------------------------------------------------------- | | Anthropic | `api.anthropic.com` | `x-api-key` | *(omit)* | Do not set `scheme`. Using `Authorization: Bearer` will cause 401s. | | OpenAI | `api.openai.com` | `Authorization` | `Bearer` | Agents may need a dummy API key so they choose API-key auth instead of OAuth. | | GitHub REST | `api.github.com` | `Authorization` | `Bearer` | Can read the `gh` token from the keyring with a custom service. | | Stripe | `api.stripe.com` | `Authorization` | `Bearer` | Stripe API keys are sent as bearer tokens. | If an application needs a provider-specific env var to select an auth path, set a placeholder such as `dummy-replaced-by-cordon`. The placeholder is not trusted: Cordon removes it and injects the real credential. ## Route changes and secret rotation Cordon loads route definitions when the proxy starts. Restart Cordon after adding or editing routes. HTTP route secrets are fetched per request, so rotating the secret value in 1Password or the keyring does not require a restart. PostgreSQL listener secrets are different; see [Secret Sources](/configuration/secret-sources#rotation-and-restarts). ## Multiple routes You can configure multiple routes for different APIs: ```toml theme={null} [[routes]] name = "anthropic" [routes.match] host = "api.anthropic.com" [routes.auth] type = "header" header_name = "x-api-key" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Anthropic API Key" field = "credential" [[routes]] name = "openai" [routes.match] host = "api.openai.com" [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "OpenAI API Key" field = "credential" [[routes]] name = "stripe" [routes.match] host = "api.stripe.com" [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` # Secret Sources Source: https://docs.codezero.io/configuration/secret-sources Configure where cordon fetches credentials — 1Password or the OS keyring. For HTTP routes, Cordon fetches credentials from external secret stores per request. PostgreSQL listeners currently resolve credentials at startup. Two sources are supported. ## Rotation and restarts | Consumer | When the secret is resolved | Rotation behavior | | -------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------- | | HTTP routes | Startup validation, then every matched request | New values are picked up on the next request. No restart required after secret rotation. | | PostgreSQL listeners | Listener startup | Restart Cordon after rotating the listener password. | Route definitions are still loaded at startup. Restart Cordon after adding, removing, or editing routes or listeners. ## 1Password Fetches secrets from 1Password vaults using the [1Password CLI](https://1password.com/downloads/command-line/) (`op`). ```toml theme={null} [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` | Field | Required | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `account` | no | 1Password account selector passed to `op --account`. Accepts the same identifiers as `op`: sign-in address, shorthand, account UUID, or user UUID. Recommended for multi-account and service setups. | | `vault` | yes | 1Password vault name within the selected account | | `item` | yes | Item name within the vault | | `field` | yes | Field label within the item (case-sensitive) | If `account` is omitted, Cordon preserves legacy behavior and lets `op` use its active/default account. Cordon warns about omitted accounts because that behavior is ambiguous in multi-account and background service environments. ### Prerequisites The `op` CLI must be installed and authenticated before starting Cordon. For installation, sign-in options, service accounts, and troubleshooting, see the [1Password guide](/guides/onepassword). ### Provider configuration (optional) If `op` is not on `PATH` (common for background services), specify the binary path explicitly in the provider config: ```toml theme={null} [secrets] [[secrets.providers]] type = "1password" path = "/opt/homebrew/bin/op" ``` ## OS Keyring Reads credentials from the operating system's built-in credential store. ```toml theme={null} [routes.auth.secret] source = "keyring" account = "stripe-api-key" ``` | Field | Required | Description | | --------- | -------- | ------------------------------------------------------------------------ | | `account` | yes | Account name (identifier for the credential) | | `service` | no | Keyring service name. Defaults to `"cordon"` when omitted. | | `decode` | no | Decoding strategy for encoded values. Currently supports `"go-keyring"`. | By default, cordon uses `cordon` as the keyring service name. If you inspect the OS keychain directly (e.g., macOS Keychain Access, `security find-generic-password -s cordon`), entries appear under the service `cordon`. ### Reading third-party keychain entries You can use `service` to read credentials stored by other tools under their own keychain entries, avoiding token duplication. For example, the GitHub CLI (`gh`) stores its token under service `gh:github.com`: ```toml theme={null} [routes.auth.secret] source = "keyring" account = "lightcap" service = "gh:github.com" decode = "go-keyring" ``` This reads `gh`'s token directly — no need to copy it into cordon's keyring. When `gh auth refresh` updates the token, cordon picks up the new value automatically. ### The `decode` field Some tools encode values before storing them in the keychain. The `decode` field tells cordon how to decode the raw keychain value before using it. | Value | Description | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `"go-keyring"` | Handles encoding from Go's [zalando/go-keyring](https://github.com/zalando/go-keyring) library. Strips `go-keyring-base64:` and base64-decodes the remainder, or strips `go-keyring-encoded:` and hex-decodes the remainder. Values without a recognized prefix are used as-is. | `decode` is only needed when the tool that stored the credential uses an encoding wrapper. Most tools (Python, Rust, native CLI tools) store raw values and don't need it. The `go-keyring` encoding is used by Go CLI tools including `gh`. ### Storing keyring credentials Use `cordon secret set` to store credentials under the default `cordon` service. The account name is independent of route names — `cordon secret set`/`cordon secret delete` take an account name directly and never read `cordon.toml` or look up routes. Use `cordon route show ` to see which account a route's `source: keyring` entry references: ```bash theme={null} cordon secret set stripe-api-key # Prompts for the secret value ``` On macOS, you can also use the native `security` CLI: ```bash theme={null} security add-generic-password -s cordon -a stripe-api-key -w 'sk_live_...' ``` `cordon secret set` always stores under the `cordon` service. When using a custom `service` to read from a third-party keychain entry, the credential is managed by that tool — you don't need to store it via cordon. ### Platform differences macOS Keychain enforces per-application access control. The binary that creates a keychain entry owns it and can read it without prompting. Any other binary triggers a system authorization dialog. **Use `cordon secret set`** to create entries owned by cordon. Entries created by other tools (e.g., `security` CLI) will prompt on every request that needs the secret. Upgrading cordon, doing a fresh `cargo build` during development, or changing the install path changes the binary identity. macOS will prompt for keychain access the first time the new binary reads the secret. When this prompt appears, click **Always Allow** (not just Allow) to permanently grant the new binary access. Because cordon resolves keyring secrets just-in-time on each request, clicking "Allow" (one-time) means the prompt reappears on the very next request — which can be milliseconds later. In SSH, headless, or detached screen/tmux sessions, the keychain is locked and cannot display a prompt. Run `security unlock-keychain ~/Library/Keychains/login.keychain-db` first, or run the command from a terminal in a desktop session (directly or via screen sharing) where the login keychain is already unlocked. See [Troubleshooting (macOS)](#troubleshooting-macos) for details. On Linux, cordon uses the D-Bus Secret Service API, typically provided by GNOME Keyring or KDE Wallet. Secrets are encrypted on disk and unlocked with the user's login session. There are no per-application ACLs — any process running as the user can read entries. Security relies on standard Unix user isolation. A Secret Service provider must be installed and running: ```bash theme={null} sudo apt install gnome-keyring # Debian/Ubuntu ``` After installing, **log out and log back in** to initialize the default "login" collection. Without this step, `cordon secret set` will fail with: ``` DBus error: Object does not exist at path "/org/freedesktop/secrets/collection/login" ``` A running D-Bus session bus is required — the keyring source does not work in containers, CI, or headless environments. ### Troubleshooting (macOS) The macOS login keychain is locked. This happens in SSH sessions, headless CI, and detached screen/tmux sessions where Keychain cannot display its authorization dialog. Unlock the login keychain before running `cordon secret set`: ```bash theme={null} security unlock-keychain ~/Library/Keychains/login.keychain-db ``` This prompts for your macOS login password. Once unlocked, keychain operations work for the remainder of the session. If you have access to the Mac directly or via screen sharing, run `cordon secret set` from a terminal in the desktop session instead — the login keychain is already unlocked there. macOS Keychain enforces per-application access control based on the binary identity. Any change to the cordon binary — upgrades, fresh builds during development, different install paths — triggers a new authorization prompt. **Preferred fix:** Re-store credentials with `cordon secret set ` so the current binary owns the entry. **Quick unblock:** Click **Always Allow** on the macOS Keychain dialog. This permanently grants the new binary access. Do not click just "Allow" — HTTP routes resolve keyring secrets per-request, so a one-time allow triggers a new prompt on every subsequent request. ### Troubleshooting (Linux) A Secret Service provider is not running or has not initialized its default collection. Fix: ```bash theme={null} sudo apt install gnome-keyring ``` Then **log out and log back in** so the keyring daemon starts and creates the "login" collection. The D-Bus session bus is not available. Verify it's reachable: ```bash theme={null} # Check for session bus address echo $DBUS_SESSION_BUS_ADDRESS # Or check for systemd user bus socket ls $XDG_RUNTIME_DIR/bus ``` If neither is available, you're not in a desktop session (e.g., SSH, container, or headless server). The keyring source requires a desktop session. The Secret Service provider is not responding. Check that gnome-keyring-daemon (or your provider) is running: ```bash theme={null} ps aux | grep gnome-keyring ``` If not running, log out and back in, or start it manually: ```bash theme={null} gnome-keyring-daemon --start --components=secrets ``` ## Mixing sources You can use different secret sources for different routes: ```toml theme={null} [[routes]] name = "stripe" [routes.match] host = "api.stripe.com" [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" [[routes]] name = "internal-api" [routes.match] host = "api.internal.example.com" [routes.auth] type = "header" header_name = "X-Api-Key" [routes.auth.secret] source = "keyring" account = "internal-api-key" ``` # TLS Source: https://docs.codezero.io/configuration/tls How cordon intercepts HTTPS traffic using a local CA certificate. Cordon performs TLS man-in-the-middle (MITM) for configured route hosts so it can inspect HTTPS requests and decide whether they are eligible for credential injection. This is the same technique used by tools like mitmproxy and Charles Proxy. HTTPS traffic to unconfigured hosts is not decrypted. It passes through as a transparent CONNECT tunnel, so the upstream server's real certificate is presented to the client. ## How it works 1. Your app sends a `CONNECT` request to the proxy for the target host 2. Cordon responds with `200 Connection Established` 3. Cordon generates a certificate for the target hostname, signed by the local CA 4. Your app establishes a TLS connection with cordon (trusting the local CA) 5. Cordon establishes a separate TLS connection with the upstream API (using the system trust store) 6. Cordon evaluates the inner request's path and method filters. On a full route match, it strips and replaces auth headers before forwarding the request. Inner requests that miss path or method filters pass through without credential changes. ## CA certificate setup The `cordon setup` command generates CA certificates automatically: ```bash theme={null} cordon setup ``` `cordon.toml` stores absolute paths to the generated CA files. Use the real paths from your generated config; see [Scopes](/configuration/overview#scopes) for certificate storage locations. ```toml theme={null} [tls] enabled = true # Substitute paths from your real `cordon.toml`. ca_cert_path = "/path/to/ca-cert.pem" ca_key_path = "/path/to/ca-key.pem" ``` ## Trusting the CA Your system and tools need to trust the CA certificate for HTTPS interception to work without certificate errors. ### System trust store ```bash theme={null} cordon trust ``` This adds the CA to the system trust store. To remove it later: ```bash theme={null} cordon untrust ``` System trust is enough for clients that read the OS trust store. Other runtimes need their own proxy or CA settings: * Use [`cordon env`](/cli/env) or the [generic tool guide](/guides/generic) for the standard proxy and CA bundle variables. * Use the [SDK compatibility guide](/guides/sdk-compatibility#runtime-ca-and-proxy-notes) for runtime-specific details such as Node.js, Java, Ruby, PHP, curl, and wget. ## Troubleshooting certificate errors Certificate failures usually mean the calling process either is not using Cordon's CA configuration or is using the raw CA cert where it needs the combined bundle. Use the same source of truth for all manual setups: ```bash theme={null} cordon env ``` Common checks: * `NODE_EXTRA_CA_CERTS` points to `ca-cert.pem` for Node.js. * `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`, and `CURL_CA_BUNDLE` point to `combined-ca.pem`, not `ca-cert.pem`. * `cordon trust` has been run for tools that use the system trust store. * Runtime-specific proxy handling is covered in [SDK Compatibility](/guides/sdk-compatibility). ## Security considerations * The CA private key is stored on disk with `0600` permissions (owner-only read/write) * The CA certificate must be explicitly trusted — cordon never modifies trust stores without user action * Per-host certificates include `SubjectAltName: DNS:` as required by modern TLS clients * The downstream connection (app to cordon) uses the local CA; the upstream connection (cordon to API) uses the system trust store — these are never mixed The CA private key grants the ability to intercept any HTTPS traffic on the machine. Keep it secure and don't share it. Treat it like an SSH private key. # Claude Code Source: https://docs.codezero.io/guides/claude-code Set up cordon to give Claude Code secure API access without exposing secrets. Cordon integrates with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) so your AI agent can make authenticated API calls without holding real credentials. Cordon can inject an `ANTHROPIC_API_KEY` for Claude's API access, but this requires adding a route for `api.anthropic.com` and setting a dummy `ANTHROPIC_API_KEY` env var — see [API key setup](#api-key-setup). If you use Claude Code with a Claude Pro/Team/Enterprise subscription (OAuth login), Anthropic API credential injection won't apply — Claude Code authenticates directly via OAuth, bypassing the proxy. This limitation applies only to Claude's own authentication; cordon supports `header` and `basic` auth types for routes to other APIs. Support for OAuth-based subscriptions is coming soon. ## Scope Claude Code setup defaults to **project scope**: `cordon.toml` lives in `$CWD`, and settings are written to `$CWD/.claude/settings.local.json`. This keeps credentials and proxy configuration isolated per repository and per checkout — `settings.local.json` is Claude Code's gitignored-by-convention override file, so git worktrees do not inherit stale proxy env vars from a tracked `settings.json`. To share a single cordon configuration across all projects, use **user scope**: ```bash theme={null} cordon setup claude-code --scope user ``` User scope writes settings to `$HOME/.claude/settings.json` and stores config at `$XDG_CONFIG_HOME/cordon/cordon.toml`. See [Scopes](/configuration/overview#scopes) for path details and trade-offs. ## Automated setup The fastest way to get started: ```bash theme={null} cordon setup claude-code ``` This: 1. Generates CA certificates (if not already present) 2. Creates a scaffold `cordon.toml` 3. Creates `tokens.toml` with a default token (if one doesn't already exist), plus a `claude-code` client token wired into the tokenized proxy URL — see [Token migration](/guides/token-migration) 4. Generates the standard proxy and CA env vars from [`cordon env`](/cli/env) 5. Configures Claude Code's settings file (`.claude/settings.local.json` for project scope, `~/.claude/settings.json` for user scope) and adds `NODE_OPTIONS` when the Node bootstrap loader is discoverable 6. Installs a [health-check hook](#health-check-hook) that blocks Claude Code when cordon is not running 7. Installs a cordon agent skill (`.claude/skills/cordon/SKILL.md` for project scope, `~/.claude/skills/cordon/SKILL.md` for user scope) 8. Offers to add routes and store secrets interactively (post-setup wizard) Any existing settings file is backed up to `.cordon.bak` before any changes are made. If setup detects cordon env vars in a project's tracked `settings.json` (from a pre-fix install), they are migrated into `settings.local.json` automatically during the next `cordon setup claude-code` run. `cordon setup claude-code` also prompts "Install as a background service so cordon starts automatically?" (default No). Answer yes, or pass `--service` to install non-interactively. If you skipped it, run `cordon service install` afterward (add `--scope user` if you set up Claude Code with `--scope user`). ### Remove the setup ```bash theme={null} cordon integration disable claude-code ``` ## Sandbox configuration (macOS) Claude Code runs tools in a macOS sandbox that manages proxy env var propagation to subprocesses. The sandbox must be **enabled** for `HTTP_PROXY`/`HTTPS_PROXY` env vars (set in your settings file) to reach tools like curl, wget, and gh. When the sandbox is disabled, these env vars are not propagated — requests bypass cordon entirely and no credential injection occurs. `cordon setup claude-code` configures these settings automatically. The resulting sandbox configuration in your Claude Code settings file (`.claude/settings.local.json` for project scope, `~/.claude/settings.json` for user scope) looks like: ```json theme={null} { "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, "network": { "allowLocalBinding": true, "allowedDomains": ["127.0.0.1"] } } } ``` * **`enabled: true`** activates the sandbox and its proxy env var propagation pipeline. Without this, subprocesses will not receive proxy env vars and cordon credential injection will silently fail. * **`autoAllowBashIfSandboxed: true`** allows Bash commands to run without individual permission prompts when the sandbox is active. * **`allowLocalBinding: true`** and **`allowedDomains: ["127.0.0.1"]`** permit sandboxed processes to connect to Cordon's loopback listener. ### Go-based tools (`gh`, `terraform`, `kubectl`, `gcloud`) If you use Go-based CLI tools through Claude Code's sandbox, you also need `enableWeakerNetworkIsolation`. Go binaries use Apple's Security.framework for TLS certificate verification, which delegates to the `trustd` daemon via Mach IPC. The sandbox blocks this by default, causing `OSStatus -26276` errors. This must be added manually — `cordon setup claude-code` does not set it by default because it reduces sandbox isolation. ```json theme={null} { "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, "enableWeakerNetworkIsolation": true, "network": { "allowLocalBinding": true, "allowedDomains": ["127.0.0.1"] } } } ``` `enableWeakerNetworkIsolation` allows sandboxed processes to communicate with `com.apple.trustd.agent`. It is strictly more secure than disabling the entire sandbox with `dangerouslyDisableSandbox: true`, but it does [reduce isolation](https://code.claude.com/docs/en/settings) by opening access to the system TLS trust service. Only add it if you need Go-based tools to work through the proxy. Users who only use curl, npm, or Node.js tools through the sandbox do **not** need `enableWeakerNetworkIsolation`. Sandbox settings are applied when Claude Code launches. After changing these settings, you must restart Claude Code for them to take effect. ## Adding routes The `cordon route`, `cordon start`, and `cordon service` commands below default to **project scope**. If you set up Claude Code with `--scope user`, append `--scope user` to each of these commands so they target `~/.config/cordon/cordon.toml` instead of `./cordon.toml`. After setup, add a route for your API provider. Claude Code's own Anthropic API-key path uses `x-api-key`: ```bash theme={null} cordon route add --name anthropic --host api.anthropic.com \ --auth-type header --header-name x-api-key \ --source keyring --account anthropic-api-key ``` Anthropic uses a raw `x-api-key` header. For bearer-style APIs, use the `Authorization` header; CLI-created `Authorization` routes use the `Bearer` scheme automatically. For example, to add a GitHub API route: ```bash theme={null} cordon route add --host api.github.com --auth-type header --header-name Authorization --source keyring --account github ``` If you chose keyring as the secret source, store the secret using the account name from the route: ```bash theme={null} cordon secret set ``` See [Routes](/configuration/routes#provider-auth-quick-reference) for provider auth types, dummy-key behavior, and route change restart guidance. ## API key setup Claude Code needs an API key env var set so it selects the API key auth path. Add a placeholder to your settings file — `.claude/settings.local.json` for project scope or `~/.claude/settings.json` for user scope: ```json theme={null} { "env": { "ANTHROPIC_API_KEY": "dummy-replaced-by-cordon" } } ``` Cordon strips this dummy key and injects the real one from your secret store at the network layer. ## Manual setup Prefer `cordon setup claude-code` for Claude Code configuration. It writes the proxy, CA, sandbox, and health-check settings consistently and backs up existing files before changing them. If setup cannot cover your environment, add the values from [`cordon env`](/cli/env) to Claude Code's `env` settings. For project scope, write `.claude/settings.local.json`; for user scope, write `~/.claude/settings.json`. The settings shape is: ```json theme={null} { "env": { "HTTPS_PROXY": "http://:@127.0.0.1:", "HTTP_PROXY": "http://:@127.0.0.1:", "https_proxy": "http://:@127.0.0.1:", "http_proxy": "http://:@127.0.0.1:", "NODE_EXTRA_CA_CERTS": "/path/to/ca-cert.pem", "SSL_CERT_FILE": "/path/to/combined-ca.pem", "REQUESTS_CA_BUNDLE": "/path/to/combined-ca.pem", "CURL_CA_BUNDLE": "/path/to/combined-ca.pem" } } ``` Use [`cordon env`](/cli/env) for the exact values, including the tokenized proxy URL — do not hand-type the token. Use [Any tool (generic)](/guides/generic) for the full env-var contract, [TLS](/configuration/tls#troubleshooting-certificate-errors) for certificate errors, and [SDK Compatibility](/guides/sdk-compatibility#node-register-import) for Node's bootstrap loader. ## Workflow Once configured, the workflow is: 1. Start cordon: `cordon start` (or use the background service) 2. Start Claude Code as usual 3. When Claude Code makes API calls that fully match configured routes, cordon transparently injects credentials 4. Claude Code never sees or logs real API keys Use `cordon doctor` to diagnose any setup issues. It checks config validity, cert paths, trust store status, and port availability. ## Health-check hook When `cordon setup claude-code` runs, it installs a `UserPromptSubmit` hook that checks whether cordon is responding before every message you send. If cordon is configured but not running, the hook **blocks the message** and shows an error — preventing you from sitting through repeated `ConnectionRefused` retries with no explanation. The hook runs `cordon status -q`, a fast health check (50ms timeout with one retry) that exits 0 if cordon is healthy and 1 if not. ### What you see when cordon is down ``` Cordon proxy is not running. Run `cordon start` or remove the hook. ``` The hook exits with code 2, which blocks the message. The `!` prefix runs shell commands directly from Claude Code's prompt without going through the AI — use it to start cordon or remove the integration even while the hook is blocking. ### Recovery options | Option | Command | Notes | | ------------------ | ------------------------------------------------ | ---------------------------------------------------------------------------------- | | Start as service | `! cordon service start` | Requires prior `cordon service install`. Runs in background. | | Start manually | `cordon start` (separate terminal) | Foreground process — keeps running until you Ctrl+C. | | Remove integration | `! cordon integration disable claude-code --yes` | Removes proxy env vars, hook, and skill. Restart session with `claude --continue`. | Hooks are loaded when Claude Code starts. If you install or remove the hook mid-session, restart with `claude --continue` for the change to take effect. ### Hook details * **Project scope**: script is installed at `.claude/cordon-status-hook.sh` in the project directory * **User scope**: script is installed at `~/.config/cordon/hooks/claude-code-status.sh` * The hook entry is added to `settings.local.json` (project) or `~/.claude/settings.json` (user) under `hooks.UserPromptSubmit` * The hook appends to existing hooks — it does not overwrite any `UserPromptSubmit` hooks you already have * Running setup again is idempotent — if the hook is already installed, it prints "Hook already installed" and moves on * `cordon integration disable claude-code` removes both the hook entry from settings and the script file * The hook script always embeds the absolute resolved `cordon.toml` path and passes `--config ` to `cordon status -q`, for both project and user scope. This ensures the hook checks the correct cordon instance even when Claude Code's working directory differs from where setup ran, such as in a git worktree. ## Troubleshooting This message comes from the health-check hook. It means cordon's proxy env vars are set in your Claude Code settings, but `cordon status -q` could not reach the health endpoint. 1. **Start cordon**: type `! cordon service start` in the Claude Code prompt, or run `cordon start` in another terminal 2. **Check status**: `! cordon status` shows whether the proxy is running, stopped, or blocked by another process 3. **Remove the integration**: if you no longer want cordon for this project, type `! cordon integration disable claude-code --yes`, then restart with `claude --continue` Claude Code uses the same proxy and CA env vars documented in [`cordon env`](/cli/env). Check [TLS troubleshooting](/configuration/tls#troubleshooting-certificate-errors), then verify those values are present in `.claude/settings.local.json` or `~/.claude/settings.json`. If you see `x509: certificate has expired` or `OSStatus -26276` errors after upgrading cordon, but `cordon doctor` shows everything healthy — your Claude Code session is still connected to the old cordon instance. Each Claude Code session runs an internal proxy that chains to cordon. After a service restart, active sessions hold stale connections to the previous process. **Fix:** Restart the Claude Code session. Exit with `/exit` and start a new session. See [`cordon upgrade`](/cli/upgrade#running-services) for the full upgrade sequence. If Go-based tools (`gh`, `terraform`, `kubectl`, `gcloud`) fail with: ``` tls: failed to verify certificate: OSStatus -26276 ``` This means Claude Code's macOS sandbox is blocking `com.apple.trustd.agent` Mach IPC. Go uses Apple's Security.framework for TLS certificate verification, which communicates with the `trustd` daemon. The sandbox blocks this by default. Add `enableWeakerNetworkIsolation` to your sandbox settings: ```json theme={null} { "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, "enableWeakerNetworkIsolation": true, "network": { "allowLocalBinding": true, "allowedDomains": ["127.0.0.1"] } } } ``` See [Sandbox configuration](#go-based-tools-gh-terraform-kubectl-gcloud) for details and trade-offs. Restart Claude Code after changing sandbox settings. Python tools may also encounter TLS errors in the sandbox, but these are more commonly resolved by ensuring `SSL_CERT_FILE` and `REQUESTS_CA_BUNDLE` point to the combined CA bundle -- see the [Certificate errors](#troubleshooting) accordion above. Verify the env vars are in Claude Code's settings file. Project scope writes `.claude/settings.local.json`; user scope writes `~/.claude/settings.json`. ```bash theme={null} # Project scope cat .claude/settings.local.json # User scope cat ~/.claude/settings.json ``` If the vars are set but Claude Code isn't routing through the proxy, use the shared [proxy not running checks](/guides/process-management#proxy-not-running-checks). If `HTTP_PROXY`/`HTTPS_PROXY` are set in your settings file but subprocesses (curl, wget, gh) don't see them — requests go directly to the internet and cordon credential injection silently fails. **Cause:** The sandbox is disabled (`"enabled": false` or absent). Claude Code's sandbox manages proxy env var propagation to subprocesses. When the sandbox is off, the proxy env vars from your settings file are not passed through, even though other env vars (like CA cert paths) may be. **Diagnosis:** Run `env | grep HTTP_PROXY` in a Claude Code Bash tool call. If the proxy vars are missing but CA cert vars are present, the sandbox is likely disabled. **Fix:** Ensure `sandbox.enabled` is `true` in your settings file: ```json theme={null} { "sandbox": { "enabled": true, "autoAllowBashIfSandboxed": true, "network": { "allowLocalBinding": true, "allowedDomains": ["127.0.0.1"] } } } ``` Restart Claude Code after changing sandbox settings. MCP servers launched by Claude Code inherit the env vars written by `cordon setup claude-code`. If a server is launched outside Claude Code, or its config replaces rather than inherits the parent environment, follow the [MCP server guide](/guides/mcp-servers). Node.js built-in `fetch` needs the Cordon bootstrap loader. `cordon setup claude-code` sets this automatically when `register.mjs` is discoverable; otherwise follow [SDK Compatibility: Node register import](/guides/sdk-compatibility#node-register-import). Restart Cordon after adding or editing route definitions. See [Routes: route changes and secret rotation](/configuration/routes#route-changes-and-secret-rotation). 1. **Verify the secret is stored**: `cordon secret set ` (find the account name with `cordon route show `) 2. **Check the header**: Anthropic uses `type: header` with `header_name: x-api-key` and no `scheme` 3. **Check the secret source**: HTTP route secrets are fetched per-request — if you changed a secret, the next request picks it up automatically # Codex Source: https://docs.codezero.io/guides/codex Set up cordon to give OpenAI Codex secure API access without exposing secrets. Cordon integrates with [OpenAI Codex](https://github.com/openai/codex) so your AI agent can make authenticated API calls without holding real credentials. Current limitations: Cordon injects API keys for Codex, not ChatGPT/OAuth session credentials. Codex works best with Cordon in non-sandboxed sessions; `--dangerously-bypass-approvals-and-sandbox` is inherently riskier because model-generated commands run without Codex's normal sandbox boundary. To run Codex outside its command sandbox, launch it with: ```bash theme={null} codex --dangerously-bypass-approvals-and-sandbox ``` For non-interactive runs, use the same flag with `exec`: ```bash theme={null} codex exec --dangerously-bypass-approvals-and-sandbox "your prompt here" ``` ## Scope Codex setup defaults to **project scope**: `cordon.toml` lives in `$CWD`, proxy env vars are written to `$CWD/.codex/.env`, and a Codex config block is written to `$CWD/.codex/config.toml`. For **full startup-time coverage**, Codex must load the project-local `.env`, so point `CODEX_HOME` at the project directory at runtime: ```bash theme={null} export CODEX_HOME="$PWD/.codex" codex --dangerously-bypass-approvals-and-sandbox ``` Add this export to your shell profile or a project `.envrc` (e.g. direnv) to automate it. To use a single cordon config across all projects, override to user scope: ```bash theme={null} cordon setup codex --scope user ``` User scope writes env vars to `$CODEX_HOME/.env` (or `$HOME/.codex/.env` if `CODEX_HOME` is not set), writes shell command env policy to `$CODEX_HOME/config.toml`, and stores config at `$XDG_CONFIG_HOME/cordon/cordon.toml`. See [Scopes](/configuration/overview#scopes) for path details and trade-offs. ## Automated setup The fastest way to get started: ```bash theme={null} cordon setup codex ``` This: 1. Generates CA certificates (if not already present) 2. Creates a scaffold `cordon.toml` 3. Creates `tokens.toml` with a default token (if one doesn't already exist), plus a `codex` client token wired into the tokenized proxy URL — see [Token migration](/guides/token-migration) 4. Writes the standard proxy and CA env vars from [`cordon env`](/cli/env) to Codex's `.env` and `[shell_environment_policy.set]` in Codex's `config.toml` 5. Installs a cordon agent skill (`.agents/skills/cordon/SKILL.md` for project scope, `$CODEX_HOME/skills/cordon/SKILL.md` for user scope) 6. Offers to add routes and store secrets interactively (post-setup wizard) Your existing `.env` is backed up to `.env.cordon.bak` before any changes are made. `cordon setup codex` also prompts "Install as a background service so cordon starts automatically?" (default No). Answer yes, or pass `--service` to install non-interactively. If you skipped it, run `cordon service install` afterward (add `--scope user` if you set up Codex with `--scope user`). ### Remove the setup ```bash theme={null} cordon integration disable codex ``` Removes the proxy env vars and Codex config block, and removes the agent skill. ## API key setup Codex needs to be configured to use API key authentication (rather than OAuth) so traffic routes through cordon. Run `codex login`, select the API key option (option 3), and enter a dummy value: ```bash theme={null} codex login # Select: API key # Enter: dummy-replaced-by-cordon ``` Cordon strips this dummy key and injects the real one from your secret store at the network layer. ## Adding routes The `cordon route`, `cordon start`, and `cordon service` commands below default to **project scope**. If you set up Codex with `--scope user`, append `--scope user` to each of these commands so they target `~/.config/cordon/cordon.toml` instead of `./cordon.toml`. After setup, add a route for OpenAI: ```bash theme={null} cordon route add --name openai --host api.openai.com \ --auth-type header --header-name Authorization --source keyring --account openai ``` If you chose keyring as the secret source, store the secret: ```bash theme={null} cordon secret set openai ``` See [Routes](/configuration/routes#provider-auth-quick-reference) for provider auth types, dummy-key behavior, and route change restart guidance. ## Manual setup Prefer `cordon setup codex` for Codex configuration. It writes the proxy and CA settings to the right files, updates the shell environment policy, and backs up existing files before changing them. If setup cannot cover your environment, copy the values from [`cordon env`](/cli/env) to the appropriate Codex `.env` file. ```bash theme={null} HTTPS_PROXY="http://:@127.0.0.1:" SSL_CERT_FILE="/path/to/combined-ca.pem" ``` Codex filters out `CODEX_*` prefixed variables from its `.env` file as a security measure, so you must use `SSL_CERT_FILE` instead of `CODEX_CA_CERTIFICATE`. If you need to set `CODEX_CA_CERTIFICATE`, it must be in your shell environment (e.g., `~/.zshrc`), not in the `.env` file. The `CODEX_HOME` env var can override the default `~/.codex/` path if Codex is installed in a non-standard location. For shell commands and tools launched by Codex, set the same values in `config.toml`: ```toml theme={null} [shell_environment_policy.set] HTTPS_PROXY = "http://:@127.0.0.1:" SSL_CERT_FILE = "/path/to/combined-ca.pem" ``` Project-local `.codex/config.toml` can help trusted projects without setting `CODEX_HOME`, but `.codex/.env` is still the full startup-time path for Codex's own network traffic. See [Any tool (generic)](/guides/generic) for the full env-var contract. ## How it works Codex is a Rust CLI that uses `reqwest` for HTTP and `rustls` for TLS. It loads `$CODEX_HOME/.env` via `dotenvy` at startup (before any threads are created), so proxy env vars are picked up automatically. It also applies `shell_environment_policy.set` from `config.toml` to shell commands and tools it launches later. For Cordon's matched-route TLS behavior and certificate troubleshooting, see [TLS](/configuration/tls). ### WebSocket fallback Codex prefers WebSocket connections (`wss://`) for the OpenAI realtime API. WebSocket connections through cordon's MITM currently fail with a TLS handshake error — Codex gracefully detects this and falls back to HTTP/SSE for the remainder of the session. The credential injection works identically on both transports; only the connection upgrade fails. WebSocket support is tracked in a future release. ## Workflow Once configured, the workflow is: 1. Start cordon: `cordon start` (or use the background service) 2. Start Codex as usual 3. When Codex makes API calls to `api.openai.com`, cordon transparently injects credentials 4. Codex never sees or logs real API keys Use `cordon doctor` to diagnose any setup issues. It checks config validity, cert paths, trust store status, and port availability. ## Troubleshooting If Codex is connecting to `chatgpt.com` or `ab.chatgpt.com` instead of `api.openai.com`, it's using the OAuth path. Run `codex login`, select option 3 (API key), and enter a dummy value. Check cordon's logs — you should see `MITM: injecting credentials route=openai` for `api.openai.com` requests. Verify the active Codex `.env` includes the CA bundle values from [`cordon env`](/cli/env), then follow [TLS troubleshooting](/configuration/tls#troubleshooting-certificate-errors). Verify the env vars are in the active Codex `.env`: ```bash theme={null} cat "$CODEX_HOME/.env" ``` If the file exists but Codex isn't routing through the proxy, use the shared [proxy not running checks](/guides/process-management#proxy-not-running-checks). Codex silently filters out all `CODEX_*` prefixed variables from its `.env` file. Use `SSL_CERT_FILE` instead, or set `CODEX_CA_CERTIFICATE` in your shell profile (`~/.zshrc` or `~/.bashrc`). Restart Cordon after adding or editing route definitions. See [Routes: route changes and secret rotation](/configuration/routes#route-changes-and-secret-rotation). 1. Verify the secret is stored: `cordon secret set openai` 2. Check the route auth config — OpenAI uses `type: header`, `header_name: Authorization`, and `scheme: Bearer` 3. HTTP route secrets are fetched per-request — if you changed a secret, the next request picks it up automatically # Database Connections Source: https://docs.codezero.io/guides/databases Inject credentials into PostgreSQL connections at the wire protocol level. Cordon can inject credentials into database connections, not just HTTP APIs. For PostgreSQL, cordon intercepts the wire protocol handshake and injects the password before the connection reaches the upstream database. Your application connects to a local port with no password and cordon handles authentication transparently. ## How it works 1. Cordon listens on a local port (e.g., `15432`) 2. Your app connects to `localhost:15432` with no password 3. Cordon intercepts the PostgreSQL authentication handshake 4. The real password, resolved when Cordon starts, is injected into the authentication handshake 5. The authenticated connection is forwarded to the upstream database Your application never sees or handles database credentials. PostgreSQL listeners currently resolve credentials at startup, not per connection. If you rotate the underlying secret, restart Cordon to pick up the new value. ## Configuration Database connections are configured as `[[listeners]]` entries in `cordon.toml`, alongside HTTP routes. See [Listeners](/configuration/listeners) for the full configuration reference. ```toml theme={null} listen = 6790 [tls] enabled = true # Substitute paths from your real `cordon.toml` (typically under ~/.config/cordon/projects//certs/). ca_cert_path = "/path/to/ca-cert.pem" ca_key_path = "/path/to/ca-key.pem" # ... your HTTP routes ... [[listeners]] name = "prod-db" port = 15432 upstream = "db.prod.example.com:5432" client_tls = "accept" [listeners.auth] type = "password" username = "app_user" [listeners.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Postgres Prod" field = "password" ``` | Field | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------------------------------------------- | | `name` | string | Yes | Identifier for the listener (used in logs) | | `port` | integer | Yes | Local port to listen on | | `upstream` | string | Yes | Upstream database host and port | | `client_tls` | string | No | Client-to-Cordon TLS mode: `accept` (default), `require`, or `disable`. | | `auth.type` | string | Yes | Auth type. Currently `password`. | | `auth.username` | string | Yes | Username to authenticate as | | `auth.secret` | object | Yes | Secret source reference. See [Secret Sources](/configuration/secret-sources). | PostgreSQL listener upstreams are configured trust decisions in v1, matching configured HTTP credential routes. Cordon resolves the configured upstream once at listener startup and connects to the selected `SocketAddr`, which pins DNS and avoids a second lookup before connecting. Private, loopback, and link-local upstream policy will be revisited post-v1 in [#531](https://github.com/codezero-io/cordon/issues/531). ## Client TLS PostgreSQL clients can request TLS from the local Cordon listener by sending a PostgreSQL `SSLRequest` before the startup packet. Configure `client_tls` on each listener: | Mode | Behavior | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `accept` | Default. Accepts TLS when Cordon can load valid `tls.ca_cert_path` and `tls.ca_key_path`; plaintext clients still work. If no usable CA is configured, Cordon declines SSL requests to preserve `sslmode=prefer` and plaintext behavior. | | `require` | Requires clients to request and complete TLS before the PostgreSQL startup packet. This mode requires `tls.ca_cert_path` and `tls.ca_key_path`. | | `disable` | Declines PostgreSQL SSL requests and continues only with clients that fall back to plaintext. | Cordon uses the same local CA configured under `[tls]` for PostgreSQL client TLS. Clients using `sslmode=verify-ca` or `sslmode=verify-full` must trust that CA, for example by running `cordon trust` or by configuring the client trust store directly. Cordon presents a loopback certificate valid for `localhost`, `127.0.0.1`, and `::1`. For `sslmode=verify-full`, connect to the local listener with one of those host identities. Cordon always requires TLS on the Cordon-to-upstream PostgreSQL leg. It sends a PostgreSQL `SSLRequest` to the upstream server and completes TLS before sending startup or authentication data. If the upstream declines TLS or certificate verification fails, Cordon fails closed and does not send credentials. ## Connecting your application Point your application at the local port instead of the upstream database: ``` postgresql://app_user@localhost:15432/mydb ``` No password in the connection string. Cordon injects it. ```bash theme={null} DATABASE_URL=postgresql://app_user@localhost:15432/mydb ``` ## Multiple databases Configure multiple PostgreSQL listeners for different databases, each on its own local port: ```toml theme={null} [[listeners]] name = "prod-db" port = 15432 upstream = "db.prod.example.com:5432" client_tls = "accept" [listeners.auth] type = "password" username = "app_user" [listeners.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Postgres Prod" field = "password" [[listeners]] name = "staging-db" port = 15433 upstream = "db.staging.example.com:5432" client_tls = "accept" [listeners.auth] type = "password" username = "staging_user" [listeners.auth.secret] source = "keyring" account = "staging-pg-pass" ``` # Any tool (generic) Source: https://docs.codezero.io/guides/generic Route any application through cordon using environment variables. Cordon ships dedicated integrations for Claude Code, Codex, Hermes, and OpenClaw. For anything else — a Next.js app, a Python service, a Go binary, a shell script — you route traffic through cordon by setting a handful of environment variables. This guide documents that contract. ## The env-var contract Cordon terminates TLS locally, which requires the calling process to: 1. Send HTTP(S) traffic through the Cordon proxy URL from `cordon env`, and 2. Trust the cordon CA so the terminated TLS handshakes validate. The env vars below express both. They are the same vars every cordon integration sets under the hood — there is nothing special about "generic" mode. | Variable | Value | Why | | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `HTTPS_PROXY` / `HTTP_PROXY` | `http://:@127.0.0.1:` when token enforcement is active; `http://127.0.0.1:` only when inactive | Most HTTP clients honor these. `` is the local Cordon scope namespace and `` comes from `listen` in `cordon.toml`. | | `https_proxy` / `http_proxy` | same | lowercase variants — required by a handful of tools (curl on some distros, wget, some Python libs). | | `NODE_EXTRA_CA_CERTS` | absolute path to `ca-cert.pem` | Node.js-specific. Node expects a raw cert, **not** a bundle. | | `SSL_CERT_FILE` | absolute path to `combined-ca.pem` | Used by OpenSSL, curl, Go. These vars *replace* the default trust store, so they need a bundle (system CAs + cordon CA) — a raw cert alone would lose every other trust anchor. | | `REQUESTS_CA_BUNDLE` | same combined bundle | Python `requests` library. | | `CURL_CA_BUNDLE` | same combined bundle | curl. | The combined bundle (`combined-ca.pem`) is generated at setup time by concatenating your system CA trust store with the cordon CA. Runtime-specific exceptions are covered in [SDK Compatibility](/guides/sdk-compatibility#runtime-ca-and-proxy-notes). Tokenized proxy URLs are sensitive local configuration; do not commit them or paste them into logs and shared chats. In tokenized proxy URLs, `` is Cordon's local token audience: `user` for the user scope or a project namespace such as `myapp-a1b2c3d4`. This is not an OAuth/OIDC audience claim; it is a local guardrail that helps Cordon reject a token presented to the wrong Cordon daemon. ## Getting the values You don't derive these by hand. `cordon env` prints them for the current scope's `cordon.toml`: ```bash theme={null} cordon env ``` Output (shell format, default): ```sh theme={null} export HTTPS_PROXY='http://myapp-a1b2c3d4:@127.0.0.1:6790' export HTTP_PROXY='http://myapp-a1b2c3d4:@127.0.0.1:6790' export https_proxy='http://myapp-a1b2c3d4:@127.0.0.1:6790' export http_proxy='http://myapp-a1b2c3d4:@127.0.0.1:6790' export NODE_EXTRA_CA_CERTS='/Users/you/.config/cordon/projects/myapp-a1b2c3d4/certs/ca-cert.pem' export SSL_CERT_FILE='/Users/you/.config/cordon/projects/myapp-a1b2c3d4/certs/combined-ca.pem' export REQUESTS_CA_BUNDLE='/Users/you/.config/cordon/projects/myapp-a1b2c3d4/certs/combined-ca.pem' export CURL_CA_BUNDLE='/Users/you/.config/cordon/projects/myapp-a1b2c3d4/certs/combined-ca.pem' ``` See [`cordon env`](/cli/env) for the full command reference (fish, dotenv, and json formats, and the `--scope` flag). ## Applying them Pick whichever fits your workflow. ### Current shell (bash / zsh) ```bash theme={null} eval "$(cordon env)" ``` ### fish ```fish theme={null} cordon env --format fish | source ``` ### direnv (`.envrc`) ```bash theme={null} eval "$(cordon env)" ``` Reload with `direnv allow` after adding. Traffic from commands run in that directory flows through cordon. ### mise (`.mise.toml`) ```bash theme={null} cordon env --format dotenv > .mise.cordon.env ``` Then reference `_.file = ".mise.cordon.env"` in your `.mise.toml`. ### dotenv (`.env`) ```bash theme={null} cordon env --format dotenv >> .env ``` ### Programmatic / CI ```bash theme={null} cordon env --format json ``` Pipe into `jq` or parse with your language's JSON library. The output is a flat object of environment variable names to string values, so scripts can safely export every top-level entry. ## Verifying it works With the env vars exported and cordon running (`cordon start`), make a request to a host you've configured a route for: ```bash theme={null} curl -v https://api.stripe.com/v1/charges ``` Expect a response from the real API with cordon injecting the Authorization header on its way out. If TLS verification fails, see [TLS troubleshooting](/configuration/tls#troubleshooting-certificate-errors). Run [`cordon doctor`](/cli/doctor) if anything feels off. ## When to use which scope * `cordon env` (default, `--scope project`) reads `./cordon.toml`. Use this when you have a per-project setup. * `cordon env --scope user` reads `~/.config/cordon/cordon.toml`. Use this for a user-wide cordon instance shared across projects. Scopes map to separate daemons with separate ports — see [Scopes](/configuration/overview#scopes) for the reference. # Hermes Agent Source: https://docs.codezero.io/guides/hermes Set up cordon to give Hermes Agent secure API access without exposing secrets. Cordon integrates with [Hermes Agent](https://hermes-agent.nousresearch.com/) so your AI agent can make authenticated API calls without holding real credentials. ## Scope Hermes setup treats the default `~/.hermes/` profile as its own cordon project. It stores config at `~/.hermes/cordon.toml` and writes proxy env vars to `~/.hermes/.env`, so Hermes is isolated from unrelated repository configs while still working across projects. Hermes does not expose `--scope`, and setup rejects the generic user-scope config at `~/.config/cordon/cordon.toml`. If you need follow-up commands such as `cordon route`, `cordon start`, `cordon env`, or `cordon service`, pass `--config ~/.hermes/cordon.toml`. Automated setup only supports the default profile in v1; `HERMES_HOME` must be unset or point at `~/.hermes`. See [Scopes](/configuration/overview#scopes) for path details and trade-offs. ## Automated setup The fastest way to get started: ```bash theme={null} cordon setup hermes ``` This: 1. Generates CA certificates (if not already present) 2. Creates a scaffold `~/.hermes/cordon.toml` 3. Creates `tokens.toml` with a default token (if one doesn't already exist), plus a `hermes` client token wired into the tokenized proxy URL — see [Token migration](/guides/token-migration) 4. Writes the standard proxy and CA env vars from `cordon env --config ~/.hermes/cordon.toml` to Hermes's `~/.hermes/.env` 5. Installs a cordon agent skill to `~/.hermes/skills/devops/cordon/SKILL.md` 6. Offers to add routes and store secrets interactively (post-setup wizard) Your existing `.env` is backed up to `.env.cordon.bak` before any changes are made. `cordon setup hermes` also prompts "Install as a background service so cordon starts automatically?" (default No). Answer yes, or pass `--service` to install non-interactively. If you skipped it, run `cordon service install --config ~/.hermes/cordon.toml` afterward. ### Remove the setup ```bash theme={null} cordon integration disable hermes ``` ## Adding routes After setup, add a route for your LLM provider with [`cordon route add --config ~/.hermes/cordon.toml`](/cli/route) and, if using the keyring secret source, store the credential with [`cordon secret set`](/cli/secret). Pass `--config ~/.hermes/cordon.toml` to `cordon route`, `cordon start`, and `cordon service` commands so they target the Hermes config. Anthropic uses `type: header` with `header_name: x-api-key` and no `scheme`. Using an `Authorization` header will result in 401 errors. ## Provider auto-detection Hermes uses env vars to auto-detect which LLM provider to use. Since cordon injects the real API key at the network layer, Hermes still needs a dummy key to select the right provider. Add a placeholder to `~/.hermes/.env`: ```bash theme={null} # Hermes sees this and selects the Anthropic provider. # Cordon strips it and injects the real key from the keychain. ANTHROPIC_API_KEY=dummy-replaced-by-cordon ``` Without this, Hermes won't know which provider to use and will fail to make API calls even though cordon has the real credentials ready to inject. ## Manual setup Prefer `cordon setup hermes` for the default Hermes profile. It writes the proxy and CA settings, generates the combined CA bundle, and backs up the existing `.env` before changing it. If setup cannot cover your environment, copy the values from [`cordon env --config ~/.hermes/cordon.toml`](/cli/env) to the relevant Hermes `.env` file manually: ```bash theme={null} HTTPS_PROXY="http://:@127.0.0.1:" HTTP_PROXY="http://:@127.0.0.1:" https_proxy="http://:@127.0.0.1:" http_proxy="http://:@127.0.0.1:" SSL_CERT_FILE="/path/to/combined-ca.pem" REQUESTS_CA_BUNDLE="/path/to/combined-ca.pem" CURL_CA_BUNDLE="/path/to/combined-ca.pem" ``` Use the exact authenticated proxy URL from `cordon env --config ~/.hermes/cordon.toml`; do not hand-type or commit the tokenized URL. Automated setup does not write to custom `HERMES_HOME` locations in v1. See [Any tool (generic)](/guides/generic) for the full env-var contract when configuring non-default profiles by hand. ## How it works Hermes uses Python's `httpx` library for HTTP, which honors `HTTPS_PROXY` by default (`trust_env=True`). The OpenAI, Firecrawl, and Exa SDKs all use `httpx` or `requests` internally, and Hermes's own Tavily client uses `httpx` directly, so all HTTP traffic routes through cordon automatically. No code changes or monkeypatching required. For Cordon's matched-route TLS behavior and certificate troubleshooting, see [TLS](/configuration/tls). ### Sandboxed environments Hermes supports several sandboxed execution backends (`TERMINAL_ENV`): `docker`, `singularity`, `modal`, `daytona`, and `ssh`. These environments run in isolated network namespaces where `127.0.0.1` refers to the container's or remote host's loopback, not the developer's machine. The cordon proxy running on the host is not reachable from inside these sandboxes without network bridging. For local execution (`TERMINAL_ENV=local`), cordon works out of the box. For sandboxed backends, network reachability varies by backend and has not been fully tested. Docker may reach the host via `host.docker.internal` on macOS/Windows, but other backends (Modal, Daytona, SSH) have their own networking models. A remote cordon proxy with network-accessible binding would be needed for full support (not yet available). ## Workflow Once configured, the workflow is: 1. Start cordon: `cordon start --config ~/.hermes/cordon.toml` (or use the background service) 2. Start Hermes as usual 3. When Hermes makes API calls that fully match configured routes, cordon transparently injects credentials 4. Hermes never sees or logs real API keys Use `cordon doctor` to diagnose any setup issues. It checks config validity, cert paths, trust store status, and port availability. ## Troubleshooting 1. **Wrong header config**: Anthropic uses `type: header` with `header_name: x-api-key` and no `scheme`. Check your `cordon.toml` route configuration. 2. **Missing dummy key**: Hermes won't select a provider without its API key env var set. Add `ANTHROPIC_API_KEY=dummy-replaced-by-cordon` to `~/.hermes/.env`. 3. **Check the secret source**: HTTP route secrets are fetched per-request — if you changed a secret, the next request picks it up automatically. 4. **Verify the secret is stored**: `cordon secret set anthropic` Verify `~/.hermes/.env` includes the CA bundle values from [`cordon env --config ~/.hermes/cordon.toml`](/cli/env), then follow [TLS troubleshooting](/configuration/tls#troubleshooting-certificate-errors). Verify the env vars are in `~/.hermes/.env`: ```bash theme={null} cat ~/.hermes/.env ``` Hermes loads this file at startup via `load_hermes_dotenv()`. If the file exists but Hermes isn't routing through the proxy, use the shared [proxy not running checks](/guides/process-management#proxy-not-running-checks) with `--config ~/.hermes/cordon.toml`. If Hermes can't determine which LLM provider to use, it's likely missing the dummy API key env var. Add the appropriate key to `~/.hermes/.env`: ```bash theme={null} ANTHROPIC_API_KEY=dummy-replaced-by-cordon # for Anthropic OPENAI_API_KEY=dummy-replaced-by-cordon # for OpenAI ``` Restart Cordon after adding or editing route definitions. See [Routes: route changes and secret rotation](/configuration/routes#route-changes-and-secret-rotation). # MCP Servers Source: https://docs.codezero.io/guides/mcp-servers Route MCP server traffic through cordon for transparent credential injection. [Model Context Protocol (MCP)](https://modelcontextprotocol.io) servers can use cordon to make authenticated API calls without holding credentials directly. ## Node.js MCP servers If you run `cordon setup claude-code`, Claude Code writes the proxy, CA bundle, and Node bootstrap env vars to its settings file. MCP servers launched by Claude Code inherit those values automatically. For manual per-server config, copy the proxy and CA vars from [`cordon env`](/cli/env) into the server's `env` block. Node.js MCP servers also need the bootstrap loader from [SDK Compatibility](/guides/sdk-compatibility#node-register-import): ```json theme={null} { "mcpServers": { "my-server": { "command": "npx", "args": ["-y", "@some/mcp-server"], "env": { "HTTPS_PROXY": "http://:@127.0.0.1:", "HTTP_PROXY": "http://:@127.0.0.1:", "https_proxy": "http://:@127.0.0.1:", "http_proxy": "http://:@127.0.0.1:", "NODE_EXTRA_CA_CERTS": "/path/to/ca-cert.pem", "SSL_CERT_FILE": "/path/to/combined-ca.pem", "REQUESTS_CA_BUNDLE": "/path/to/combined-ca.pem", "CURL_CA_BUNDLE": "/path/to/combined-ca.pem", "NODE_OPTIONS": "--import \"/absolute/path/to/@codezero-io/cordon/register.mjs\"" } } } } ``` Prefer `cordon setup claude-code` when possible. It writes an absolute `register.mjs` path automatically, so MCP servers do not depend on Node package resolution. ## Python MCP servers Python MCP servers using `requests` or `httpx` respect `HTTPS_PROXY` and `HTTP_PROXY` automatically. For HTTPS interception, copy the combined CA bundle vars emitted by [`cordon env`](/cli/env): ```json theme={null} { "mcpServers": { "my-python-server": { "command": "python", "args": ["-m", "my_mcp_server"], "env": { "HTTPS_PROXY": "http://:@127.0.0.1:", "HTTP_PROXY": "http://:@127.0.0.1:", "https_proxy": "http://:@127.0.0.1:", "http_proxy": "http://:@127.0.0.1:", "SSL_CERT_FILE": "/path/to/combined-ca.pem", "REQUESTS_CA_BUNDLE": "/path/to/combined-ca.pem", "CURL_CA_BUNDLE": "/path/to/combined-ca.pem" } } } } ``` See [Any tool (generic)](/guides/generic) for the full env-var contract, including lowercase proxy vars and `CURL_CA_BUNDLE`. ## Other languages Most HTTP clients in Go, Rust, and other languages respect the standard `HTTPS_PROXY` environment variable. Set the proxy env vars in the MCP server's environment and ensure the CA certificate is trusted, either through `cordon trust` or a language-specific CA bundle variable. Some languages require additional setup beyond setting environment variables. See the [SDK Compatibility guide](/guides/sdk-compatibility) for a full compatibility matrix — notably Ruby's `net/http`, Java's `java.net.http`, and PHP's `curl_init()` do not read proxy env vars by default. # 1Password Source: https://docs.codezero.io/guides/onepassword 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 ``` 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 account, vault, item, and field under `routes.auth.secret`: ```toml theme={null} [routes.auth.secret] source = "1password" account = "my-team" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` | Field | Required | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `account` | no | 1Password account selector passed to `op --account`. Accepts the same identifiers as `op`: sign-in address, shorthand, account UUID, or user UUID. Recommended for multi-account and service setups. | | `vault` | yes | 1Password vault name within the selected account | | `item` | yes | Item name within the vault | | `field` | yes | Field label within the item (case-sensitive) | A 1Password **vault** is a container of items within an **account** (e.g., `my-team.1password.com`). If `account` is omitted, Cordon preserves legacy behavior and lets `op` use its active/default account. Cordon warns about omitted accounts because that behavior is ambiguous in multi-account and background service environments. ### Finding your account identifier The `account` field accepts the same identifiers that `op --account` does: **sign-in address**, **shorthand**, **account UUID**, or **user UUID**. The account's **display name does not work** — `op` does not recognize it. Run `op account list` to see all accounts and their identifiers: ```bash theme={null} op account list ``` This prints a table with columns including the sign-in address (e.g., `my-team.1password.com`), account UUID, and user UUID. Use any of these values for the `account` field. The sign-in address or shorthand is usually the most readable choice: ```toml theme={null} [routes.auth.secret] source = "1password" account = "my-team.1password.com" vault = "Engineering" item = "Stripe API Key" field = "secret_key" ``` Do not use your 1Password account's display name (e.g., "My Team") as the `account` value. The `op` CLI does not recognize display names and the secret lookup will fail. ### Multi-account setup If your routes reference secrets across multiple 1Password accounts, each account must be signed in before starting Cordon. Sign in to each account separately: ```bash theme={null} # Sign in to each account that your routes reference eval $(op signin --account my-team.1password.com) eval $(op signin --account personal.1password.com) ``` Verify all accounts are active: ```bash theme={null} op account list ``` Set the `account` field on every 1Password route so Cordon knows which account to query. Without `account`, `op` falls back to its default account, which may not be the one that holds the secret. ```toml theme={null} # Route using a work account [[routes]] name = "stripe" [routes.match] host = "api.stripe.com" [routes.auth] type = "header" header_name = "Authorization" scheme = "Bearer" [routes.auth.secret] source = "1password" account = "my-team.1password.com" vault = "Engineering" item = "Stripe API Key" field = "secret_key" # Route using a personal account [[routes]] name = "personal-api" [routes.match] host = "api.example.com" [routes.auth] type = "header" header_name = "X-Api-Key" [routes.auth.secret] source = "1password" account = "personal.1password.com" vault = "Personal" item = "Example API Key" field = "credential" ``` You can add or update the `account` field on existing routes with the CLI: ```bash theme={null} cordon route add --host api.stripe.com --auth-type header --header-name Authorization \ --source 1password --account my-team.1password.com --vault Engineering --item "Stripe API Key" --field secret_key cordon route edit stripe --account my-team.1password.com ``` **Background services** (via `cordon service install`) run outside your shell session. For multi-account setups, every 1Password route must include `account` — there is no interactive session to choose a default. If you use 1Password's desktop app integration with biometric unlock, `op` authenticates through the desktop app automatically and all accounts remain available. ### Startup warning for omitted accounts When Cordon starts and finds a 1Password route without an `account` field, it logs a warning: ``` 1Password secret ref omits account; add `account` to avoid ambiguous multi-account resolution in services ``` This warning means `op` will use its default or last-used account for that route, which may resolve to the wrong account — especially in multi-account or background service environments. Fix the warning by adding the `account` field to the affected routes with `cordon route edit`: ```bash theme={null} cordon route edit --account ``` ### 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" ``` 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. ### 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 the equivalent of: ```bash theme={null} op item get "" --account "" --vault "" --fields "label=" --format json ``` When `account` is omitted, Cordon omits `--account` and lets `op` choose its active/default account. 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 on full route matches (stripped and replaced unconditionally after host, path, and method filters match) 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. 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 **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 account/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 account, vault, and field labels) op item get "Stripe API Key" --account "my-team" --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 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](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. 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" --account "Account" --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 `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" --account "Account" --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`) # OpenClaw Source: https://docs.codezero.io/guides/openclaw Set up cordon to give OpenClaw secure API access without exposing secrets. Cordon integrates with [OpenClaw](https://openclaw.ai) so the gateway and any agent it spawns can reach LLM providers, web fetch APIs, and web search providers through cordon — with credentials injected at the network layer instead of stored in OpenClaw's config. ## Scope OpenClaw setup treats the default `~/.openclaw/` profile as its own cordon project. It stores config at `~/.openclaw/cordon.toml`, updates `~/.openclaw/openclaw.json`, and configures the user-level launchd/systemd daemon (`ai.openclaw.gateway`) to trust cordon's CA. This keeps OpenClaw isolated from unrelated repository configs while still serving projects through one gateway. OpenClaw does not expose `--scope`, and setup rejects the generic user-scope config at `~/.config/cordon/cordon.toml`. If you need follow-up commands such as `cordon route`, `cordon start`, `cordon env`, or `cordon service`, pass `--config ~/.openclaw/cordon.toml`. Automated setup only supports the default profile in v1; `OPENCLAW_CONFIG_PATH` and `OPENCLAW_STATE_DIR` must be unset or point at the default `~/.openclaw` locations. See [Scopes](/configuration/overview#scopes) for path details and trade-offs. ## Automated setup The fastest way to get started: ```bash theme={null} cordon setup openclaw ``` This: 1. Detects the `openclaw` CLI on `PATH` (fails fast with a clear message if missing, before touching any cordon-side state) 2. Generates CA certificates (if not already present) 3. Creates a scaffold `~/.openclaw/cordon.toml` 4. Creates `tokens.toml` with a default token (if one doesn't already exist), plus an `openclaw` client token — see [Token migration](/guides/token-migration) — used to build the authenticated `proxy.proxyUrl` in the next step 5. Writes `proxy.enabled=true` and the authenticated `proxy.proxyUrl` directly to `~/.openclaw/openclaw.json` 6. Reinstalls the OpenClaw daemon (`openclaw daemon uninstall && openclaw daemon install`) with `NODE_EXTRA_CA_CERTS` and `NODE_USE_SYSTEM_CA=1` layered into the install-time env 7. Restarts the daemon so the new proxy config takes effect 8. Probes the daemon with `openclaw daemon status` as a smoke test 9. Installs a cordon agent skill to `~/.openclaw/skills/cordon/SKILL.md` 10. Offers to add routes and store secrets interactively (post-setup wizard) The daemon reinstall is the disruptive step — the OpenClaw gateway is stopped and restarted as part of setup. Active sessions are interrupted. `cordon setup openclaw` also prompts "Install as a background service so cordon starts automatically?" (default No). Answer yes, or pass `--service` to install non-interactively. If you skipped it, run `cordon service install --config ~/.openclaw/cordon.toml` afterward. ### Remove the setup ```bash theme={null} cordon integration disable openclaw ``` Teardown is intentionally best-effort: partial failures (daemon already stopped, skill file already gone) print warnings but don't abort scripted cleanup chains. The teardown reinstalls the OpenClaw daemon with cordon's CA env vars explicitly stripped from the install subprocess, so even users who have `eval $(cordon env)` exported in their shell get a fully cordon-free service definition. ## Adding routes After setup, add a route for your LLM provider with [`cordon route add --config ~/.openclaw/cordon.toml`](/cli/route) and, if using the keyring secret source, store the credential with [`cordon secret set`](/cli/secret). Pass `--config ~/.openclaw/cordon.toml` to `cordon route`, `cordon start`, and `cordon service` commands so they target the OpenClaw config. Anthropic uses `type: header` with `header_name: x-api-key` and no `scheme`. Using an `Authorization` header will result in 401 errors. ## Provider auto-detection OpenClaw inspects `env.vars` in `~/.openclaw/openclaw.json` (or the per-provider auth profile) to decide which LLM provider to use. Since cordon injects the real API key at the network layer, OpenClaw still needs a value present to select the right provider. A dummy is fine: ```jsonc theme={null} { "env": { "vars": { "ANTHROPIC_API_KEY": "dummy-replaced-by-cordon" } } } ``` Without a value, OpenClaw will fail to select the provider even though cordon has the real credential ready to inject. (If you previously had a real key in `env.vars`, replace it — leaving it there isn't unsafe, but it defeats the point of cordon.) ## Manual setup Prefer `cordon setup openclaw` for the default OpenClaw profile. It writes `~/.openclaw/openclaw.json` directly, then runs the daemon reinstall, restart, and smoke test in the required order. If setup cannot cover your environment, the equivalent by hand is: ```bash theme={null} # 1. Get cordon's env (only NODE_EXTRA_CA_CERTS survives OpenClaw's service-env filter) eval "$(cordon env --config ~/.openclaw/cordon.toml)" # 2. Reinstall the daemon so the launchd/systemd service env is regenerated openclaw daemon uninstall && openclaw daemon install # 3. Activate the managed proxy in ~/.openclaw/openclaw.json # Set proxy.enabled=true and proxy.proxyUrl="$HTTPS_PROXY". # 4. Restart for the proxy config to take effect openclaw daemon restart ``` The `proxy.proxyUrl` must use `http:` — OpenClaw's `isSupportedProxyUrl` rejects `https:`. Cordon's proxy listener is plain HTTP (loopback only), which matches. ## How it works OpenClaw's gateway is a Node.js process. When `proxy.enabled=true` is set, its [`startProxy()`](https://github.com/openclaw/openclaw/blob/main/src/infra/net/proxy/proxy-lifecycle.ts) lifecycle installs a global `undici` dispatcher routing all outbound HTTP through `proxy.proxyUrl`, and sets `OPENCLAW_PROXY_ACTIVE=1` on the process. This covers: * **LLM provider API calls** (Anthropic, OpenRouter, etc.) — gateway-side `fetch` traffic * **`web_fetch` tool calls** — strict-mode fetch takes the `canUseManagedProxy` branch * **`web_search` tool calls** — explicitly uses the trusted-env-proxy fetch wrapper The `NODE_EXTRA_CA_CERTS` env var lives in OpenClaw's launchd/systemd service env file (`~/.openclaw/service-env/ai.openclaw.gateway.env`), pointing at cordon's CA so the gateway trusts cordon's MITM certs. `NODE_USE_SYSTEM_CA=1` keeps system roots loaded alongside the cordon CA, so non-cordon-fronted endpoints still validate normally. Only `NODE_EXTRA_CA_CERTS` and `NODE_USE_SYSTEM_CA` survive OpenClaw's service-env filter. Standard `HTTPS_PROXY` / `HTTP_PROXY` env vars are deliberately stripped (`daemon/service-env.ts:readServiceProxyEnvironment`) — only `OPENCLAW_PROXY_URL` is forwarded. Setup uses the config-driven path (`proxy.proxyUrl` in `~/.openclaw/openclaw.json`) instead, which `startProxy()` reads at gateway startup. For cordon's matched-route TLS behavior and certificate troubleshooting, see [TLS](/configuration/tls). ### Browser tool Older OpenClaw versions failed with a CDP 502 when using `openclaw browser start` with `proxy.enabled=true`. The gateway's managed proxy was intercepting its own loopback CDP connection. This was [fixed upstream](https://github.com/openclaw/openclaw/pull/83255) — run `openclaw upgrade` to get the fix. If you're on an older version and can't upgrade immediately, you can use an external `@playwright/mcp` stdio MCP server via `openclaw mcp add` as a workaround — the stdio transport bypasses the managed proxy. ## Workflow Once configured, the workflow is: 1. Start cordon: `cordon start --config ~/.openclaw/cordon.toml` (or use the background service) 2. The OpenClaw daemon is already running (set up by `cordon setup openclaw`) 3. When OpenClaw makes API calls that fully match configured routes, cordon transparently injects credentials 4. OpenClaw never sees or logs real API keys Use `cordon doctor` to diagnose any setup issues. It checks config validity, cert paths, trust store status, and port availability. ## Troubleshooting 1. **Wrong header config**: Anthropic uses `type: header` with `header_name: x-api-key` and no `scheme`. Check your `cordon.toml` route configuration. 2. **Missing dummy key**: OpenClaw won't select a provider without a value in `env.vars` (or the auth profile). Add `ANTHROPIC_API_KEY=dummy-replaced-by-cordon` (or equivalent) to `~/.openclaw/openclaw.json`'s `env.vars`. 3. **Check the secret source**: HTTP route secrets are fetched per-request — if you changed a secret, the next request picks it up automatically. 4. **Verify the secret is stored**: `cordon secret set anthropic` Verify the OpenClaw gateway's launchd service env carries cordon's CA: ```bash theme={null} grep NODE_EXTRA_CA_CERTS ~/.openclaw/service-env/ai.openclaw.gateway.env ``` The value should point at cordon's `ca-cert.pem`. If empty or pointing at the macOS default (`/etc/ssl/cert.pem`), re-run `cordon setup openclaw` (it reinstalls the daemon with the correct env). Then follow [TLS troubleshooting](/configuration/tls#troubleshooting-certificate-errors) for runtime cert issues. Confirm OpenClaw activated the managed proxy at startup. Tail the gateway log and look for the activation line: ```bash theme={null} grep "managed proxy" ~/.openclaw/logs/gateway.log ``` Expected: `[proxy] routing process HTTP traffic through external proxy http://:@127.0.0.1:`. If absent, verify `openclaw config get proxy` returns `enabled: true` and a valid `proxyUrl`. Then use the shared [proxy not running checks](/guides/process-management#proxy-not-running-checks) with `--config ~/.openclaw/cordon.toml`. Fixed in newer OpenClaw versions. Run `openclaw upgrade` to get the fix. If you can't upgrade, see [Browser tool](#browser-tool) above for a workaround. Earlier versions of the integration could re-snapshot inherited `NODE_EXTRA_CA_CERTS` from the user's shell into the new service env, even on `cordon integration disable openclaw`. The current setup explicitly strips those env vars from the install subprocess. If you upgraded from an earlier cordon, re-run `cordon integration disable openclaw && cordon setup openclaw` once to land the fix. Restart Cordon after adding or editing route definitions. The OpenClaw gateway picks up cordon routes per-request, so it doesn't need its own restart for route changes — only when `proxy.enabled` or `proxy.proxyUrl` themselves change. See [Routes: route changes and secret rotation](/configuration/routes#route-changes-and-secret-rotation). # Process Management Source: https://docs.codezero.io/guides/process-management Run cordon alongside your application using Procfiles, launchd, or systemd. Cordon needs to be running before your application starts making API calls. There are two approaches. Project scope is the default; use `--config` when you need to target an integration-specific config path such as `~/.hermes/cordon.toml` or `~/.openclaw/cordon.toml`. * **Procfile** (recommended for development) — start Cordon alongside your app in a process manager. Simple, no system-level installation, stops when you stop developing. * **Background service** (optional) — install Cordon as a launchd/systemd service for a project or user scope. Starts on login, restarts on failure. Useful when you want the proxy always available without manually starting it. ## Procfile (foreman / overmind) The simplest approach for development. Use `cordon wait` to block until the proxy is ready: ``` proxy: cordon start --config cordon.toml web: cordon wait && npm run dev ``` `cordon wait` polls the health endpoint until it returns `200`, then exits. Your application starts only after credentials are loaded and the proxy is accepting connections. ## Background service Install cordon as an OS-managed service that starts automatically: ```bash theme={null} cordon service install --config /path/to/cordon.toml ``` The service is installed as a launchd user agent. It starts on login and restarts on failure. ```bash theme={null} # Install cordon service install --config /path/to/cordon.toml # Check status cordon status # Uninstall cordon service uninstall ``` The service is installed as a systemd user service. ```bash theme={null} # Install cordon service install --config /path/to/cordon.toml # Check status cordon status # Uninstall cordon service uninstall ``` ### Named instances Run multiple cordon instances with different configs: ```bash theme={null} cordon service install api-proxy --config ~/configs/api-cordon.toml cordon service install db-proxy --config ~/configs/db-cordon.toml ``` ## Health endpoint The health endpoint is available at `GET /health` once the proxy binds its listener: | Status | Response | Meaning | | ---------------------- | ----------------------- | ------------------------------------------------------------ | | `200` | `{"status":"ok"}` | Proxy is ready — secrets loaded, accepting connections | | `503` | `{"status":"starting"}` | Internal pre-ready state; normally not observable by callers | | *(connection refused)* | *(no response)* | Proxy has not finished starting | Before the listener binds, there is no open port (connection refused). Readiness is set synchronously immediately after `TcpListener::bind()` succeeds and before the proxy begins accepting connections, so callers normally see `/health` go straight from connection refused to `200`. Process supervisors can still distinguish between "not started yet" (connection refused) and "ready" (200). ## Proxy not running checks Use these checks from any integration when requests fail with connection refused or the tool reports that Cordon is down: ```bash theme={null} cordon status cordon doctor ``` If you know the configured port, the health endpoint should return `200`: ```bash theme={null} curl http://127.0.0.1:/health ``` If the proxy is stopped, start it manually or through the installed service: ```bash theme={null} cordon start cordon service start ``` For integration-specific configs such as Hermes or OpenClaw, add `--config ~/.hermes/cordon.toml` or `--config ~/.openclaw/cordon.toml` to these commands. ## Startup sequence The proxy starts in a strict order: 1. Parse and validate `cordon.toml`. Exit on invalid config. 2. Validate HTTP route secrets from configured sources. Exit if any fail. 3. Load TLS certificates when needed for HTTP MITM or PostgreSQL client TLS. 4. Resolve PostgreSQL listener credentials at startup, if listeners are configured. 5. Bind listener on `127.0.0.1:`. Readiness is set immediately afterward, so `/health` starts returning `200` from this point. 6. Begin accepting connections. # SDK Compatibility Source: https://docs.codezero.io/guides/sdk-compatibility Which HTTP clients and AI SDKs work with cordon out of the box, and how to configure the ones that don't. Cordon works transparently with any HTTP client that respects the `HTTPS_PROXY` / `HTTP_PROXY` environment variables. Some languages and SDKs use custom HTTP implementations or different proxy mechanisms that need additional configuration. ## Compatibility matrix | Language / Runtime | SDK / Library | Works with env vars? | Workaround | | ------------------ | ---------------- | -------------------- | --------------------------------------------------------------------------------------- | | CLI | `curl` | Partial | HTTP: only lowercase `http_proxy`. HTTPS: both cases work. (see below) | | CLI | `wget` | Partial | Only reads lowercase `http_proxy` and `https_proxy` (see below) | | Go | `net/http` | Yes | None needed | | Rust | `reqwest` | Yes | None needed | | Python | `requests` | Yes | None needed | | Python | `httpx` | Yes | None needed | | Python | `urllib` | Yes | None needed | | Ruby | `net/http` | Partial | HTTP: prefers lowercase `http_proxy`. HTTPS: **not supported** via env vars (see below) | | C# / .NET | `HttpClient` | Yes | None needed | | Java | `java.net.http` | No | JVM proxy properties and Java trust configuration (see below) | | PHP | `curl_init()` | Partial | HTTP: only lowercase `http_proxy`. HTTPS: both cases work. (see below) | | PHP | Guzzle | Yes (CLI only) | Reads uppercase `HTTP_PROXY` in CLI mode | | Node.js | `undici.fetch()` | Yes (with register) | `--import @codezero-io/cordon/register` plus `NODE_EXTRA_CA_CERTS` | | Node.js | built-in `fetch` | Yes (with register) | `--import @codezero-io/cordon/register` plus `NODE_EXTRA_CA_CERTS` | | Node.js | `node-fetch` | No | Use `http-proxy-agent` package | | Node.js | `axios` | Yes (with helper) | `@codezero-io/cordon/axios` | | Node.js | `got` | Yes | Respects env vars natively | ## Runtime CA and proxy notes Use [`cordon env`](/cli/env) for the standard env vars and certificate paths. The notes below only cover runtimes with special proxy or trust-store behavior. ### curl The `curl` CLI uses libcurl, which deliberately ignores uppercase `HTTP_PROXY` as a security measure against the [httpoxy vulnerability](https://httpoxy.org/) (CVE-2016-5385) — only lowercase `http_proxy` is read. For **HTTPS** requests, both `HTTPS_PROXY` and `https_proxy` are accepted (the httpoxy attack vector does not apply to HTTPS). ### wget `wget` has its own HTTP implementation (it does **not** use libcurl). It only reads **lowercase** environment variables for both protocols: `http_proxy` and `https_proxy`. Uppercase variants (`HTTP_PROXY`, `HTTPS_PROXY`) are ignored entirely. `cordon setup` sets both uppercase and lowercase proxy variables automatically. If you configure the proxy manually, make sure to set lowercase `http_proxy` and `https_proxy` in addition to their uppercase counterparts. ### Java Java's built-in `java.net.http.HttpClient` does **not** read `HTTP_PROXY` or `HTTPS_PROXY` environment variables. Instead, it uses JVM system properties: ```bash theme={null} java -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=6790 \ -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=6790 \ -jar your-app.jar ``` Java also uses its own trust configuration. If HTTPS requests fail with certificate errors, import the Cordon CA into the JVM trust store or point the JVM at an application-specific trust store: ```bash theme={null} keytool -importcert -alias cordon \ -file /path/to/ca-cert.pem \ -keystore "$JAVA_HOME/lib/security/cacerts" \ -storepass changeit -noprompt ``` Use the `ca-cert.pem` path from `cordon env` or your generated `cordon.toml`. ### Node runtime Node.js does not use the system trust store, and built-in `fetch` does not read proxy env vars by default. Use both pieces: ```bash theme={null} NODE_EXTRA_CA_CERTS="/path/to/ca-cert.pem" \ NODE_OPTIONS="--import @codezero-io/cordon/register" \ node your-app.js ``` `NODE_EXTRA_CA_CERTS` points to the raw Cordon CA cert, not `combined-ca.pem`. The register import is described in [Node register import](#node-register-import). ### PHP PHP's `curl_init()` uses libcurl. For **HTTP** requests, it deliberately ignores uppercase `HTTP_PROXY` as a security measure against the [httpoxy vulnerability](https://httpoxy.org/) (CVE-2016-5385) — only lowercase `http_proxy` is read. For **HTTPS** requests, both `HTTPS_PROXY` and `https_proxy` are accepted. [Guzzle](https://docs.guzzlephp.org/) (the most popular PHP HTTP client) handles this differently — it reads uppercase `HTTP_PROXY` at the application layer and explicitly sets `CURLOPT_PROXY`, but **only in CLI mode** (`PHP_SAPI === 'cli'`). In web/CGI contexts, Guzzle ignores `HTTP_PROXY` for the same httpoxy security reasons. For HTTP traffic: set **both** `HTTP_PROXY` and `http_proxy` to cover raw PHP curl and Guzzle. For HTTPS traffic: `HTTPS_PROXY` works for both raw curl and Guzzle. ### Ruby Ruby's `net/http` only supports proxy env vars for **HTTP** requests. It prefers the lowercase form `http_proxy` and will warn when uppercase `HTTP_PROXY` is used: ``` warning: The environment variable HTTP_PROXY is discouraged. Use http_proxy. ``` For **HTTPS** requests, Ruby's `net/http` does **not** automatically read `HTTPS_PROXY` or `https_proxy` from the environment. HTTPS proxy support requires explicit configuration in code: ```ruby theme={null} proxy_uri = URI.parse(ENV['HTTPS_PROXY']) Net::HTTP.new(uri.host, uri.port, proxy_uri.host, proxy_uri.port).start do |http| http.use_ssl = true response = http.request(Net::HTTP::Get.new(uri)) end ``` Ruby applications making HTTPS requests will **not** route through the proxy via environment variables alone. This may require code changes in Ruby applications that need HTTPS proxy support. ## Streaming support Cordon streams response bodies through without buffering, so SSE (Server-Sent Events) and chunked transfer encoding responses work correctly. This is critical for AI API integrations that use `stream: true` — tokens are forwarded to the client as they arrive from the upstream API, with no added latency. ## AI / LLM SDKs The major AI SDKs use custom `fetch` implementations and do **not** respect `HTTPS_PROXY` env vars or undici's global dispatcher on their own. However, `@codezero-io/cordon/register` patches `globalThis.fetch` to inject a proxy dispatcher transparently — no code changes needed. ### Automatic (recommended) Use the [Node register import](#node-register-import). It works for the Anthropic SDK, OpenAI SDK, and any other library that uses `globalThis.fetch` internally. For TLS interception, also set `NODE_EXTRA_CA_CERTS` as described in [Node runtime](#node-runtime). ### Manual (if register import is not an option) Both the Anthropic and OpenAI SDKs support explicit proxy configuration via `fetchOptions.dispatcher`: ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; import * as undici from 'undici'; const client = new Anthropic({ fetchOptions: { dispatcher: new undici.ProxyAgent('http://myapp-a1b2c3d4:@127.0.0.1:6790'), }, }); ``` ```typescript theme={null} import OpenAI from 'openai'; import * as undici from 'undici'; const client = new OpenAI({ fetchOptions: { dispatcher: new undici.ProxyAgent('http://myapp-a1b2c3d4:@127.0.0.1:6790'), }, }); ``` Both the Anthropic and OpenAI SDKs are generated by [Stainless](https://www.stainlessapi.com/), which acquires `globalThis.fetch` at client constructor time. Any other Stainless-generated SDK (e.g., Cloudflare, Lithic, Modern Treasury) will work the same way with `@codezero-io/cordon/register` — no code changes needed. ### Other AI SDKs | SDK | Status | | -------------------- | -------------- | | Vercel AI SDK | Not yet tested | | LangChain.js | Not yet tested | | Google Generative AI | Not yet tested | | AWS Bedrock | Not yet tested | | Cohere | Not yet tested | | Mistral | Not yet tested | We're actively testing more SDKs. If you've tested one not listed here, let us know so we can add it. ## Node register import The `@codezero-io/cordon/register` import patches `globalThis.fetch` to inject a proxy dispatcher into all outbound requests. It also sets undici's global dispatcher for code that uses `undici.fetch()` directly. If you use the bare `@codezero-io/cordon/register` specifier, `@codezero-io/cordon` must be installed in the project/current working directory that launches Node. A global npm install is not automatically visible to Node's package resolution. Integration setup commands such as `cordon setup claude-code` avoid this by writing an absolute `register.mjs` path when the npm-installed package is discoverable. ```bash theme={null} node --import @codezero-io/cordon/register your-app.js ``` Or via `NODE_OPTIONS`: ```bash theme={null} NODE_OPTIONS="--import @codezero-io/cordon/register" npm run dev ``` Or as a direct import in your entry file (must be the first import): ```js theme={null} import '@codezero-io/cordon/register'; ``` This works for applications using `globalThis.fetch`, `undici.fetch()`, and fetch-based SDKs like Anthropic and OpenAI. Libraries with their own HTTP stack still need their own integration path: axios has `@codezero-io/cordon/axios`, while clients like `node-fetch` still require explicit proxy-agent configuration. # Proxy Tokens Source: https://docs.codezero.io/guides/token-migration How Cordon authenticates local clients before injecting credentials. Cordon can require a token for requests to configured route hosts. Full route matches still decide whether a request receives upstream credentials. This prevents an unrelated same-user process from connecting to the loopback proxy and receiving injected API credentials. ## How it works * `tokens.toml` enables token enforcement for a scope. * Authenticated proxy URLs use HTTP Basic proxy auth: `http://:@127.0.0.1:`. * The Basic username is Cordon's local token audience: `user` for the user scope or a project namespace such as `myapp-a1b2c3d4`. This is not an OAuth/OIDC `aud` claim and does not provide third-party token attestation. * Configured HTTP route hosts and CONNECT routes require a valid `Proxy-Authorization` token when enforcement is active. Path and method filters still decide whether an inner request receives injected credentials. * Unmatched forwarding does not require a token, but malformed, duplicate, or invalid `Proxy-Authorization` is still rejected while enforcement is active. * Missing `tokens.toml` preserves legacy behavior: configured route hosts remain unauthenticated and `cordon start` logs a warning. * If a running daemon has already observed `tokens.toml`, deleting the file does not silently reopen token-protected route-host access. Restart Cordon without `tokens.toml` if you intentionally want legacy unauthenticated mode. ## Enable tokens `cordon setup` creates tokens automatically — both base setup and integration setup (`cordon setup claude-code`, `cordon setup codex`, etc.) generate a `tokens.toml` with a default token. Integration setup additionally creates a client token and wires it into the tool's proxy URL. The default token is the general-purpose proxy credential for a Cordon instance. Client tokens are named proxy credentials managed for configured clients such as Claude Code, Codex, Hermes, and OpenClaw. In the current token model, all valid tokens grant the same proxy access inside their local token audience; client tokens exist for independent rotation, revocation, setup/remove cleanup, and logging. Workload identity is verified separately. For existing setups created before token enforcement, re-run setup: ```bash theme={null} # Re-run for a specific integration cordon setup claude-code --yes cordon setup codex --yes cordon setup hermes --yes # Or base setup for standalone usage cordon setup --yes eval "$(cordon env --config ./cordon.toml)" ``` Setup output redacts tokenized URLs. `cordon env` is the command intended to print full authenticated values for shells, dotenv files, direnv, or mise. Running daemons watch `tokens.toml` and pick up new, rotated, or revoked tokens on the next configured-route-host request. Restarting the daemon is still safe, but is not required for token-only changes. ## Rotate tokens ```bash theme={null} cordon token rotate cordon token rotate default cordon token rotate claude-code cordon token rotate --all ``` Omitting the token name rotates the default token. For supported clients (`claude-code`, `codex`, `hermes`, and `openclaw`), rotation updates the tool's tokenized proxy URL before writing the new token file. Restart any already-running tool process that inherited the old URL. The daemon picks up the new token file automatically. ## Revoke client tokens ```bash theme={null} cordon token revoke codex ``` For supported clients, revoke removes matching tokenized proxy settings after removing the token from `tokens.toml`. The daemon picks up the revocation automatically. ## JSON output `cordon env --format json` remains a flat object of environment variable names to string values. Existing scripts that read `.HTTPS_PROXY` or export every top-level entry continue to work; when token enforcement is active, proxy values include `:@`. ## Handling tokenized URLs Proxy tokens are not upstream API credentials, but they grant access to Cordon's token-protected route-host proxy path for that scope while the daemon is running. Path and method filters still decide whether any request receives injected credentials. Treat tokenized proxy URLs as sensitive local configuration: * Do not commit token-bearing integration files. * Do not paste tokenized URLs into issues, logs, or shared chat. * Use `cordon token rotate ` if a tokenized URL is exposed. * Use `cordon doctor` to verify token enforcement status. Cordon setup refuses to write token-bearing files inside a Git repository unless both the final target and its atomic temp-file pattern are untracked and covered by `.gitignore`; merely untracked is not enough. For example, project Claude Code setup needs both `.claude/settings.local.json` and `.claude/.settings.json.tmp.*` ignored. Audience binding prevents accidental cross-scope proxy mixups, such as a project token being presented to a user-scope daemon or another project's daemon. It does not bind the token to a process, working directory, binary, user session, OAuth/OIDC issuer, or remote verifier. A complete proxy URL remains a bearer credential for the matching local daemon until the token is rotated or revoked. # Installation Source: https://docs.codezero.io/installation Install the Cordon CLI and optional Cordon Monitor macOS app. Install the Cordon CLI first. It runs the local proxy and provides the setup, configuration, and service commands. ## CLI ```bash theme={null} npm install -g @codezero-io/cordon ``` This installs the `cordon` binary globally. Platform-specific binaries are bundled for: * macOS ARM64 (Apple Silicon) * macOS x64 (Intel) * Linux x64 (glibc 2.17+ — RHEL 7+, Ubuntu 18.04+, Debian 9+) * Linux ARM64 (glibc 2.17+ — RHEL 7+, Ubuntu 18.04+, Debian 9+) Requires Node.js 20.18.1 or later. ## Verify installation ```bash theme={null} cordon --version ``` ## Cordon Monitor macOS app Cordon Monitor is an optional macOS menu bar app for observing local Cordon instances. It shows which project and user-scope instances are present, whether they are running, which ports they use, and the routes/listeners loaded from their configs. It is observability-only: Cordon Monitor does not start, stop, or reconfigure Cordon, and it does not handle credentials. Use the `cordon` CLI for setup and control. Observability-only companion app for macOS 14+. Signed and notarized. Verify the downloaded DMG before opening it. Open the DMG and drag **Cordon Monitor** to **Applications**. To verify the download, place the DMG and checksum file in the same directory and run: ```bash theme={null} shasum -a 256 -c Cordon-Monitor-.dmg.sha256 ``` Replace `` with the version you downloaded (e.g. `0.3.1`). Cordon Monitor does not install the `cordon` CLI. Install the CLI above to run the proxy. ## Prerequisites Cordon needs a secret source to fetch credentials from. You'll need at least one of: Install the [1Password CLI](https://1password.com/downloads/command-line/) (`op`) and sign in: ```bash theme={null} # Install (macOS) brew install --cask 1password-cli # Sign in op signin ``` The `op` CLI must be authenticated before starting cordon. Cordon uses the operating system's built-in credential store: * **macOS**: Keychain Access — no additional setup needed. * **Linux**: Secret Service API (via D-Bus), typically provided by GNOME Keyring or KDE Wallet. Requires a desktop session. On Linux, ensure a Secret Service provider is installed: ```bash theme={null} sudo apt install gnome-keyring # Debian/Ubuntu ``` After installing, **log out and log back in** so the keyring daemon starts and creates a default "login" collection. The OS keyring requires a desktop session with D-Bus. It does not work in containers, CI, or headless servers. Store credentials with the `cordon secret set` command (see [Secret Sources](/configuration/secret-sources)). # Introduction Source: https://docs.codezero.io/introduction The credential containment layer that mediates credential usage at the moment of need. Applications, agents, and pipelines never hold real secrets. ## What is Cordon? Cordon is a credential containment layer by [Codezero](https://codezero.io). It sits between the systems that protect credentials (vaults, identity providers, policy engines) and the systems that consume them: applications, AI agents, developer environments, CI/CD pipelines. Instead of handing credentials to a runtime, Cordon mediates credential use at the network boundary so secrets never enter the consuming runtime. ## Possession is the vulnerability Credentials sprawl far beyond the vaults designed to protect them: .env files, dotfiles, CI/CD logs, Docker layers, shell history, and environment variables. Inside runtimes, they become the critical path for exploits: leaking through crash dumps, dependencies, and debug endpoints. AI agents multiply the exposure with every tool call and subprocess they spawn. Cordon eliminates the sprawl by ensuring credentials never leave the containment layer. It scales from a single developer's laptop to shared team environments to enterprise-wide deployments. Same containment model, wider boundary. ## Developer Edition These docs cover the **Developer Edition** of Cordon: a free local gateway that runs on your machine. It transparently intercepts outbound HTTP/HTTPS requests and TCP connections and injects credentials from [1Password](https://1password.com) or your OS keyring at the network layer. No code changes. No SDK wrappers. Your applications and AI agents make normal network calls and never hold a single credential. Credentials stay out of environment variables, logs, crash dumps, child processes, and `ps` output entirely. Cordon injects them at the network layer. Set `HTTPS_PROXY` / `HTTP_PROXY` and most HTTP clients route through Cordon automatically. See the [SDK and language compatibility guide](/guides/sdk-compatibility) for details. Rotate an HTTP route secret in your secret store. The next matching request picks it up automatically. No restarts needed. Integrate with native keychains, secrets managers, and the infrastructure you already invested in. ## How it works Your app makes a normal API call or database connection. Cordon sits in the middle, transparent to your application. Cordon checks the destination against your configured routes in `cordon.toml`. Cordon retrieves the credential from 1Password or the OS keyring and injects it into the outbound request. Your code doesn't send any auth headers. Cordon adds them entirely. The request continues to the upstream service with the injected credentials. Your code never holds the real secret. Install Cordon via npm Get up and running in minutes ## Beyond Developer The Developer Edition is free and runs locally with no account required. The credential containment layer also extends to shared and enterprise-wide environments: * **Teams**: A shared containment layer for engineering teams running agents and pipelines together. Bring your own identity provider, vault, and policy engine. * **Enterprise**: One containment layer for every outbound request your organization makes. Self-hosted and air-gapped deployments, identity-aware audit trails, and negotiated SLAs. [Learn more at codezero.io](https://codezero.io). # Quickstart Source: https://docs.codezero.io/quickstart Get cordon running and proxying your first API request in 5 minutes. This guide walks you through setting up cordon to inject a credential into an HTTPS request — using [httpbin.org/headers](https://httpbin.org/headers) so you can see the injected header in the response. It's also a good way to confirm everything is working as expected before configuring your real API routes. Cordon is project-first — running `cordon setup` in a project directory creates a `cordon.toml` there with the project's routes and credentials isolated from other projects. A user-wide scope is also available for tools that span projects. See [Scopes](/configuration/overview#scopes) for details. ## 1. Run setup ```bash theme={null} cordon setup ``` The wizard generates CA certificates, creates a config file, and walks you through adding your first route. Say yes when asked to trust the CA certificate. Then say yes when prompted to add a route, enter `httpbin.org` as the host, `httpbin` as the route name, `header` as the auth type, `Authorization` as the header name, choose `keyring` as the secret source, and enter `httpbin` as the keyring account. Cordon automatically uses the `Bearer` scheme for `Authorization` header routes. Say no when asked to install cordon as a background service for this quickstart. After setup, store a test token in the keyring: ```bash theme={null} cordon secret set httpbin # When prompted, enter any value — e.g. "my-secret-token" ``` On macOS, `cordon secret set` stores credentials via the system keyring using an entry that cordon owns. This avoids the repeated "allow access to keychain" permission dialogs you'd see if cordon tried to read credentials stored by another application. ## 2. Start the proxy ```bash theme={null} cordon start ``` You'll see output like: ``` INFO cordon: listening on 127.0.0.1:6790 INFO cordon: route "httpbin" → httpbin.org (header) INFO cordon: health endpoint ready ``` The port (`6790` above) is the one allocated by `cordon setup` and will vary — check your terminal output or run `cordon status` to see the actual port. ## 3. Route traffic through the proxy In a separate terminal: ```bash theme={null} eval "$(cordon env)" curl https://httpbin.org/headers ``` httpbin echoes back all request headers. You should see your injected credential in the response: ```json theme={null} { "headers": { "Accept": "*/*", "Authorization": "Bearer my-secret-token", "Host": "httpbin.org", "User-Agent": "curl/8.7.1" } } ``` Notice the `Authorization` header — you never sent it. Cordon intercepted the request, matched the `httpbin.org` route, and injected the credential from your keyring. You can add more routes with `cordon route add`, or edit existing ones with `cordon route edit `. Prefer those commands over hand-editing `cordon.toml`; they validate the route and write the safer configuration for you. ## Next steps One-command setup for AI agent workflows Detailed 1Password setup and security model Configure 1Password and OS keyring All CLI commands and options # Security Model Source: https://docs.codezero.io/security/overview Architecture, design principles, and trust boundaries for credential injection. Cordon is a local credential-injection proxy. It intercepts HTTP/HTTPS traffic on the loopback interface and injects API credentials from external secret stores. This page describes the security architecture, trust boundaries, and design rationale. This document describes the Community (local) deployment model. Network-facing deployment models (Team/Enterprise) extend the trust boundary and are documented separately as they ship. ## Design principles These principles govern all security decisions in the local deployment model. ### Structural security over configuration-dependent security Security properties are enforced by code, not by operators setting the right flags. There are no environment variables, CLI flags, or config options that weaken the security posture. If a property matters, it is hardcoded or enforced by the type system. Examples: * The bind address is hardcoded to `127.0.0.1` — there is no option to bind to another address. * For unmatched outbound traffic, the SSRF denylist is always on — there is no option to disable it. Configured HTTP credential routes and `denylist_exceptions` entries are documented exemptions. * For HTTPS routes and PostgreSQL upstreams, upstream TLS verification uses the system root store — there is no option to skip certificate validation. ### Credential injection, not credential management Cordon fetches credentials from external vaults and injects them into outbound requests. It never persists, encrypts, or manages a credential store of its own. There is no Cordon-managed database to breach. The proxy's security does not depend on defending a credential store — it depends on the security of credential sources the developer already trusts and controls. ### No infrastructure in the request path The proxy runs on the developer's machine. Outbound requests go directly from the proxy to the upstream API — no intermediate cloud services, no shared multi-tenant servers, no SaaS relay. External trust is limited to the credential source (OS keyring or vault provider), which the developer already trusts and controls. Out-of-band update checks and telemetry are separate from proxied application traffic and are listed in [Telemetry and update checks](#telemetry-and-update-checks). ### Defense in depth through the type system The `Secret` type enforces zeroization, log prevention, and explicit access at compile time. `#![forbid(unsafe_code)]` prevents bypassing these guarantees. Security properties that depend on developer discipline are replaced by properties that depend on compilation. ## Trust boundaries ### Credential lifecycle Credentials pass through five stages. There is no "at rest in Cordon" stage for API credentials — the proxy holds no credential database, no encrypted store, and no API credential material on disk. | Stage | Location | Protection | | ----------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | At rest | External vault (1Password) or OS keyring | Vault/OS-managed encryption | | In transit (vault → proxy) | 1Password CLI stdout or D-Bus session bus | Process-scoped; no network exposure | | In memory | `Secret` type on the proxy's task stack | Zeroize on drop, no `Debug`/`Display`, token-gated boundary access | | In transit (proxy → upstream) | HTTPS request, plain HTTP request, or PostgreSQL connection | HTTPS and PostgreSQL use TLS with system root CA validation; plain HTTP routes remain cleartext and should only be configured intentionally | | After use | Zeroed | Guaranteed by Rust `Drop` implementation | ### Explicit trust dependencies Cordon trusts the following components. Compromise of any of these is outside the proxy's trust boundary. * **The local operating system** — process isolation, memory protection, keyring access control. A compromised OS is outside the trust boundary. * **The credential source** — 1Password or OS keyring returns the correct secret. Cordon does not verify secret correctness, only that resolution succeeded. When using the `op` CLI backend, the pre-authenticated session may be accessible to other same-user processes depending on the authentication method — see [1Password security considerations](/guides/onepassword#security-considerations). * **The upstream TLS PKI** — for HTTPS routes and PostgreSQL upstreams, system root certificates validate upstream identity. Cordon uses the system trust store and provides no mechanism to override or disable certificate validation. * **The developer's route and denylist-exception configuration** — configured HTTP route hosts are an explicit trust decision. Cordon uses a full route match (host plus any path and method filters) to authorize credential injection; in v1, configured route hosts bypass private/link-local/loopback denylist blocking even when optional credential-injection filters miss, while still using origin-bound host checks and DNS pinning. `denylist_exceptions` entries similarly carve specific `host:port` pairs out of the denylist without credential injection. Misconfigured routes can direct credentials to unintended destinations; misconfigured exceptions can expose internal services to agent-initiated traffic. * **The installed agent skill file** — `cordon setup` writes an agent skill file to disk that tells the AI agent how to use Cordon. The authoritative content is embedded in the binary, but the installed copy is a plain text file that a same-user process could modify. Installed files are set to read-only (`0444`) and `cordon doctor` detects content mismatches, but there is no cryptographic integrity check at agent read time. See the [threat model](/security/threat-model#agent-skill-file-tampering) for details. ## Telemetry and update checks Cordon does not send proxied application requests through Codezero infrastructure. The CLI and macOS menu bar app make out-of-band HTTPS requests to `https://api.codezero.dev/cordon/v1/check` for update checks and lightweight telemetry. | Collection point | Event | Purpose | | ---------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------- | | First `cordon start` update check | `install` | Initial install signal and update check | | Later `cordon start` update checks | `start` | Active-use signal and update check | | `cordon setup` | `setup_ok` or `setup_error` | Setup completion signal bucketed by result | | `cordon doctor` | `doctor_ok`, `doctor_warning`, or `doctor_error` | Troubleshooting signal bucketed by diagnostic result | | Cordon Monitor launch | `monitor_launch` | Menu bar app active-use signal with initial instance counts | | Cordon Monitor refresh | `monitor_refresh` | Fires on manual refresh or when discovery detects a change in instance or route counts | | Cordon Monitor settings toggle | `monitor_settings_changed` | Records when the user enables or disables usage telemetry | CLI requests include `cordon_version`, `os`, `arch`, and `event`. When CLI telemetry is enabled, `cordon start` creates and sends an opaque install ID (`cordon_id`) stored under the Cordon config directory. `cordon setup` and `cordon doctor` send that install ID when it already exists, but do not create it before the first `cordon start` install event. Setup telemetry includes only coarse setup metadata: `setup_kind` (`base`, `claude_code`, `codex`, `hermes`, or `openclaw`), `scope` (`project` or `user`), `yes_flag` (whether `--yes` was passed), and `trust_mode` (`trust`, `no_trust`, or `prompt`). It does not send config paths, project names, hosts, token audiences, or error strings. Cordon Monitor requests include `monitor_version`, `os`, `arch`, `event`, and `app`. When monitor telemetry is enabled, the app sends its own opaque install ID (`monitor_id`), reads the CLI's install ID from disk (`cordon_id`) to allow correlation, and includes coarse instance counts: total instances, running instances, route count, service-managed count, and manually started count. Cordon does not send route hosts, config paths, service names, secret source names, credential values, Authorization headers, proxied traffic, individual setup errors, or individual doctor check messages. Cordon Monitor checks for updates on launch by default. Usage telemetry is also enabled by default and can be toggled via Settings > Share Anonymous Usage Data in the menu bar app without disabling launch update checks. Telemetry and update checks can also be disabled with environment variables: | Environment variable | Effect | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CORDON_NO_TELEMETRY=1` | Disables the install ID. `cordon start` and Cordon Monitor launch still check for updates without the ID; `cordon setup`, `cordon doctor`, and non-launch Cordon Monitor telemetry events are not sent. | | `DO_NOT_TRACK=1` | Same telemetry opt-out behavior as `CORDON_NO_TELEMETRY=1`. Only the value `1` is treated as enabled. | | `CORDON_NO_UPDATE_CHECK=1` | Disables automatic update-check and telemetry network calls. `cordon upgrade` bypasses this and always checks the network. | Cordon is a security tool that handles credentials on behalf of your applications. Update checks are the primary mechanism for learning about security advisories and patched releases. We strongly recommend keeping update checks enabled even if you disable telemetry with `CORDON_NO_TELEMETRY` or `DO_NOT_TRACK`. Disabling update checks with `CORDON_NO_UPDATE_CHECK` means you will not be notified of security fixes until you check manually. ## Credential storage Cordon delegates credential storage entirely to external providers. The proxy holds no credential database, no encrypted store, and no API credential material on disk. HTTP route secrets exist in the proxy's memory only during active request injection; PostgreSQL listener credentials are resolved at startup and held while the listener runs. | Source | Config value | Storage | Encryption | | ---------- | ------------ | ------------------------------------- | ------------------------------ | | OS keyring | `keyring` | macOS Keychain / Linux Secret Service | OS-managed (encrypted at rest) | | 1Password | `1password` | 1Password vault | 1Password-managed | ### Platform differences **macOS Keychain** encrypts entries at rest and enforces per-application access control. Only the binary that created an entry can read it without triggering an authorization dialog. **Linux Secret Service** (GNOME Keyring, KDE Wallet) encrypts entries on disk and unlocks them with the user's login session. There are no per-application ACLs — any process running as the user can read entries. Security relies on Unix user isolation. On Linux, secrets are transmitted to the Secret Service provider over the D-Bus session bus without transport-layer encryption (no DH key exchange). This is a deliberate trade-off: the session bus is a Unix socket restricted to the current user, so any process that could observe bus traffic already has same-user code execution and could query the Secret Service directly. The trust boundary is the session bus access control, not encryption on top of it. ## In-memory secret handling Secrets in memory are protected with multiple layers: * **Zeroized on drop** — secret values are overwritten with zeros when no longer needed, reducing the window for memory scraping * **No Debug or Display** — the `Secret` type has no `Debug` or `Display` impl. Attempts to log or format a secret fail at compile time, not at runtime. * **Token-gated boundary access** — plaintext is accessible only through callback methods that require a capability token. Each protocol boundary module defines its own token with a private constructor, confining plaintext access to the specific module that needs it. Compile-fail tests enforce this from external crates. * **No unsafe code** — the core library forbids unsafe code at compile time HTTP route credentials are fetched per request and dropped after injection. PostgreSQL listener credentials are resolved at startup and retained in memory for the listener lifetime. ## Network security ### Loopback-only binding The proxy always binds to `127.0.0.1`. The config only accepts a port number, not a bind address — it is structurally impossible to configure a non-loopback address. This ensures the proxy is only accessible from the local machine. ### Proxy token enforcement and scope audience When `tokens.toml` exists for a scope, configured HTTP route hosts require `Proxy-Authorization: Basic`. Full route matches still decide whether credentials are injected. The Basic username is Cordon's local token audience: `user` for user scope, or the project namespace (`-`) for project scope. The Basic password is the default or client token value from that scope's `tokens.toml`. This audience is a local Cordon scope binding, not OAuth/OIDC `aud` semantics. It is included so a proxy URL generated for one local Cordon instance is rejected by another instance even if the token value was copied across scopes by mistake. Audience mismatch is rejected before token validation; Cordon logs the expected audience but does not log or trust the client-supplied username. Client token names (`codex`, `claude-code`, `hermes`, `openclaw`, or `default`) are resolved only after the token value validates. Successful injection logs can include both dimensions: ```text theme={null} audience=project-a1b2c3d4 token=codex audience=user token=default ``` Token audience binding is a guard against accidental wrong-proxy usage, not workload identity. Tokens remain bearer credentials: any same-user process that can read a tokenized proxy URL can use that URL until the token is rotated or revoked. Client token names support independent rotation, revocation, cleanup, and logging; they do not create per-route authorization by themselves. ### Auth header stripping For full route matches, Cordon unconditionally strips inbound `Authorization`, `Proxy-Authorization`, and the configured credential header, then replaces auth with the credential from the configured secret source. This is not conditional — the proxy does not check whether the inbound header matches a placeholder, is empty, or contains a real credential. Strip-and-replace is always applied only after host, path, and method filters match. This prevents credential passthrough attacks where a manipulated agent sends a valid credential in the Authorization header hoping the proxy will forward it unchanged to an unintended destination. ### SSRF protection All unmatched HTTP forwarding is subject to SSRF validation. Configured HTTP credential routes are exempt from private/link-local/loopback denylist blocking in v1: a route is an explicit trust decision by the developer and is allowed to target internal APIs, VPN/private endpoints, PrivateLink services, staging environments, and localhost development services. `denylist_exceptions` entries also carve specific `host:port` pairs out of the denylist without credential injection — see [Denylist exceptions](#denylist-exceptions) below. For traffic that does not match a configured route or denylist exception, there is no way to bypass the IP denylist. Cordon acts as a credential oracle — any process that can reach it can cause authenticated requests to configured upstream APIs. For untrusted runtimes (AI agents, LLM-generated code), this creates a potential SSRF exposure if left unmitigated. For unmatched traffic, Cordon addresses this with an always-on IP denylist that blocks connections to internal infrastructure (cloud metadata, private networks, localhost services) before they leave the proxy. For unmatched HTTP traffic, blocked ranges include: * Private networks: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` * Loopback: `127.0.0.0/8` * Link-local / cloud metadata: `169.254.0.0/16` (blocks AWS/GCP/Azure metadata endpoints) * CGNAT: `100.64.0.0/10` * IPv6 equivalents: `::1`, `fc00::/7`, `fe80::/10`, and more * IPv6 transition addresses (IPv4-mapped, NAT64, 6to4, Teredo, ISATAP) have their embedded IPv4 extracted and re-checked Configured HTTP credential routes skip private/link-local/loopback denylist checks in v1. They authorize credential injection and upstream selection for the configured host, but they are not a general protection against malicious same-user callers. `denylist_exceptions` entries similarly carve specific `host:port` pairs out of the denylist, but without credential injection. Post-v1 policy work will revisit the default and likely require explicit private-upstream opt-ins. ### Denylist exceptions Cordon permits outbound requests to any public destination by default; it does not maintain an egress allowlist. The `denylist_exceptions` list (a top-level key in `cordon.toml`) carves specific `host:port` pairs out of the always-on SSRF denylist without credential injection. This is useful for local development services (databases, mock APIs) that the proxy needs to reach but that do not require credential injection. ```toml theme={null} # top-level in cordon.toml denylist_exceptions = ["localhost:54321", "localhost:4201"] ``` | Property | Detail | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **Format** | Each entry must be `host:port`. Port is required — there are no wildcard or host-only entries. | | **Denylist bypass** | Entries bypass the entire SSRF denylist, not just loopback. An exception for a private-network host will be allowed. | | **Loopback expansion** | Loopback entries auto-expand: `localhost:N`, `127.0.0.1:N`, and `[::1]:N` are treated as equivalent. Configuring any one of these allows all three. | | **No credential injection** | Unlike routes, exceptions do not inject credentials. Requests are forwarded unmodified. | | **Trust model** | Same operator opt-in trust model as configured routes. Each entry is an explicit decision by the developer to permit a denylisted destination. | **DNS pinning:** DNS pinning still applies to matched and unmatched HTTP forwarding: after DNS resolution, the proxy connects to the selected address directly (not the hostname), closing the TOCTOU window against DNS rebinding. For unmatched traffic, resolved IPs are checked against the denylist before that connection. DNS pinning is not the same as private-IP blocking. ### TLS interception For HTTPS routes, Cordon performs TLS MITM using a locally-generated CA certificate. See [TLS configuration](/configuration/tls) for details. * The CA private key is stored with `0600` permissions * The CA cert must be explicitly trusted by the user * Per-host certificates use `SubjectAltName` (not CN) as required by modern clients * Upstream TLS verification uses the system root certificate store. There is no environment variable, CLI flag, or configuration option to disable certificate validation. Plain HTTP routes are forwarded as HTTP. Do not configure credential routes for cleartext upstreams unless that is intentional. # OWASP Risk Coverage Source: https://docs.codezero.io/security/owasp How Cordon addresses risks identified by OWASP across agentic AI, LLM, web, API, and CI/CD security frameworks. Cordon addresses credential-related risks identified across multiple OWASP frameworks. This page maps specific OWASP risks to Cordon's mitigations, organized by framework with the most relevant listed first. Cordon is not a complete solution for every risk listed — it specifically targets the credential exfiltration and secret exposure attack surface. Where Cordon provides partial mitigation or reduces blast radius rather than fully eliminating a risk, this is noted. Unless noted, statements about per-request credential resolution apply to HTTP routes. PostgreSQL listener credentials are held by the local proxy while the listener runs. ## OWASP Top 10 for Agentic Applications (2026) The [OWASP Top 10 for Agentic Applications](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) identifies the most critical security risks facing autonomous AI agents — systems that plan, act, and make decisions across complex workflows. This is the most directly relevant framework for Cordon's primary use case. | Risk | How Cordon Helps | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **ASI01 – Agent Goal Hijack** | A hijacked agent cannot exfiltrate credentials because credentials never enter the agent's context window, environment variables, or tool outputs. The proxy injects credentials at the HTTP layer, outside the agent's reach. | | **ASI02 – Tool Misuse** | For unmatched destinations, the SSRF denylist prevents agents from using the proxy to reach internal infrastructure (cloud metadata, private networks, localhost services). Route scoping limits which upstream APIs receive injected credentials; configured routes and `denylist_exceptions` entries are explicit operator trust decisions that carve destinations out of the denylist — routes inject credentials, exceptions allow connectivity only. | | **ASI03 – Identity & Privilege Abuse** | Cordon never caches credentials in the agent's environment. There are no API tokens, SSH keys, or session tokens for the agent to reuse, escalate, or persist across sessions. HTTP route credentials are zeroized in proxy memory after each use. | | **ASI04 – Supply Chain Vulnerabilities** | Compromised MCP servers, plugins, or RAG connectors cannot steal credentials they never see. Even if a malicious tool is invoked, it receives authenticated responses without accessing the underlying secret. | | **ASI05 – Unexpected Code Execution** | Even if an agent executes arbitrary code (shell commands, scripts, package installation), credentials are not in environment variables, files, or process memory accessible to the agent. They exist only in the proxy's memory during request injection. | ASI06 (Memory Poisoning), ASI07 (Inter-Agent Communication), ASI08 (Cascading Failures), ASI09 (Human-Agent Trust), and ASI10 (Rogue Agents) address risks at the agent orchestration layer. Cordon does not directly mitigate these, but its architecture limits the blast radius — a rogue or poisoned agent cannot leverage Cordon to exfiltrate credentials or access systems beyond its configured routes. ## OWASP Agentic AI Threats and Mitigations (v1.0) The [Agentic AI Threats and Mitigations](https://genai.owasp.org/resource/agentic-ai-threats-and-mitigations/) document provides a more granular 15-threat taxonomy organized by agentic AI lifecycle phase. The Top 10 above distills these into a ranked list. Additional threats relevant to Cordon: | Threat | How Cordon Helps | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **T3 – Privilege Compromise** | Agents cannot accumulate or inherit credentials over time. Cordon holds no credential cache in the agent runtime. HTTP requests resolve credentials from the external vault and zeroize them after injection. | | **T8 – Repudiation & Untraceability** | Cordon logs route matches, upstream destinations, and request metadata (never credentials). This provides an independent audit trail of which API calls were made through the proxy, separate from agent-controlled logs. | | **T11 – Unexpected RCE & Code Attacks** | Sandbox escapes and code injection attacks cannot reach credentials because credentials are not present in the agent's execution environment. The proxy is a separate process with its own memory space. | | **T14 – Human Attacks on Multi-Agent Systems** | Insiders who inject themselves into agent workflows cannot extract credentials from the agent — the agent genuinely does not possess them. Credential access requires direct access to the vault or to the proxy's process memory. | ## OWASP Top 10 for LLM Applications (v1.1) The [OWASP Top 10 for LLM Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/) focuses on content generation risks. Several items have a direct credential dimension. | Risk | How Cordon Helps | | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **LLM01 – Prompt Injection** | The primary credential exfiltration vector in LLM applications. Cordon eliminates this by keeping credentials outside the context window entirely — there is nothing for a prompt injection to extract. | | **LLM06 – Sensitive Information Disclosure** | LLM outputs cannot contain credentials the model never received. Cordon ensures API keys, tokens, and passwords never appear in prompts, tool outputs, or agent memory. | | **LLM07 – Insecure Plugin Design** | Plugins and tools receive authenticated HTTP responses without ever holding the credentials that authorized the request. A compromised plugin cannot leak secrets it does not possess. | | **LLM08 – Excessive Agency** | Route scoping and the SSRF denylist bound what an over-autonomous agent can do through the proxy. Even with excessive agency, the agent can only authenticate to explicitly configured upstream APIs. Configured routes and `denylist_exceptions` entries are explicit operator trust decisions; exceptions allow connectivity without credential injection. | ## OWASP Top 10 (Web Application, 2021) The [OWASP Top 10](https://owasp.org/www-project-top-10/) is the most widely recognized application security framework. Credential injection addresses several of its categories. | Risk | How Cordon Helps | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **A02 – Cryptographic Failures** | Secrets never exist in application memory, config files, or environment variables where weak encryption or accidental plaintext exposure could occur. HTTPS and PostgreSQL upstream legs use TLS with no certificate-validation bypass; plain HTTP routes remain cleartext and should only be configured intentionally. | | **A05 – Security Misconfiguration** | Centralizing credential injection removes per-application secret handling. Developers cannot misconfigure credential storage (hardcoded keys, plaintext config files, overly permissive env vars) because the application never touches credentials. | | **A07 – Identification and Authentication Failures** | Consistent, centralized auth injection eliminates per-application credential handling bugs. The proxy applies credentials uniformly — no risk of inconsistent auth across different code paths. | | **A09 – Security Logging and Monitoring Failures** | Cordon's `Secret` type makes it structurally impossible to log credentials. Applications that never receive credentials cannot accidentally log them — the most common cause of credential exposure in production logs. | ## OWASP API Security Top 10 (2023) The [OWASP API Security Top 10](https://owasp.org/API-Security/) addresses risks specific to API-driven architectures. | Risk | How Cordon Helps | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **API2 – Broken Authentication** | API credentials are injected consistently by the proxy, not managed per-endpoint by application code. Eliminates bugs where authentication is applied inconsistently across API calls. | | **API8 – Security Misconfiguration** | Verbose error messages, debug endpoints, and misconfigured applications cannot leak credentials they do not possess. The proxy handles auth injection independently of application configuration. | ## OWASP Top 10 CI/CD Security Risks (2023) The [OWASP CI/CD Security Risks](https://owasp.org/www-project-top-10-ci-cd-security-risks/) are directly relevant to AI coding agent workflows, which operate in a CI/CD-adjacent threat model — untrusted code execution with access to the process environment. | Risk | How Cordon Helps | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CICD-SEC-3 – Dependency Chain Abuse** | Compromised dependencies that scan environment variables or files for secrets find nothing — credentials exist only in the proxy's process memory during request injection. | | **CICD-SEC-4 – Poisoned Pipeline Execution** | Attacker-manipulated build or execution steps cannot steal credentials from the environment because credentials are not there. Even with full code execution, the attacker must compromise the proxy process itself. | | **CICD-SEC-5 – Insufficient PBAC** | Route-based credential injection acts as a form of pipeline-based access control — each route determines which upstream API receives which credential, rather than granting broad access to all secrets. | | **CICD-SEC-6 – Insufficient Credential Hygiene** | The most directly relevant CI/CD risk. Cordon is a direct mitigation: secrets never appear in environment variables, dotfiles, config files, or source code. HTTP route credentials are resolved from external vaults at request time and zeroized after use. | # Threat Model Source: https://docs.codezero.io/security/threat-model Systematic analysis of threats, mitigations, and residual risks using the STRIDE framework. This page uses the STRIDE framework (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) to systematically analyze threats against the local deployment model. For each category, we describe the threat scenario, the mitigation implemented, and the residual risk we acknowledge. ## S — Spoofing **Threat:** A process impersonates a legitimate client to obtain injected credentials. **Mitigation:** Loopback-only binding limits reachability to local processes. When `tokens.toml` exists for the scope, requests to configured route hosts require a valid default or client token; full route matches still decide whether credentials are injected. Tokenized proxy URLs bind that token to the local scope audience (`user` or the project namespace), so a URL generated for one Cordon instance is rejected by another instance with a different audience. Unmatched forwarding remains unauthenticated and is guarded by the SSRF denylist instead of token enforcement. **Residual risk:** Missing `tokens.toml` preserves legacy unauthenticated configured-route-host behavior. Tokens are still bearer credentials: any process that can read a token-bearing local integration file can use that complete proxy URL until the token is rotated or revoked. Audience binding reduces accidental wrong-proxy use; it does not prove process identity or prevent deliberate reuse of a copied URL. `denylist_exceptions` entries bypass the denylist without token enforcement, but they do not trigger credential injection — they are connectivity-only trust decisions. ## T — Tampering **Threat:** An attacker modifies requests in transit to redirect credentials to an unintended destination. **Mitigation:** Auth header stripping replaces inbound auth headers unconditionally. For unmatched destinations, the SSRF denylist blocks private and internal IP destinations. Origin-bound injection and upstream TLS prevent man-in-the-middle on HTTPS and PostgreSQL upstream legs. **Residual risk:** Configured HTTP routes are trusted destinations in v1 and can target private/internal hosts. If DNS resolution returns an IP controlled by an attacker and the route config matches that host, the credential will be sent. DNS pinning closes the TOCTOU window but cannot prevent a legitimately-resolved malicious IP. ### Agent skill file tampering **Threat:** An attacker modifies the installed agent skill file to inject malicious instructions — for example, directing the agent to exfiltrate secrets, skip `cordon doctor`, or route requests to attacker-controlled endpoints. **Mitigation:** The authoritative skill content is embedded in the binary at compile time via `include_str!` and cannot be modified without replacing the binary. Installed skill files are set to read-only permissions (`0444`) to prevent casual or automated modification. `cordon doctor` compares installed skills against the embedded source of truth and warns on content mismatches or loosened permissions. **Residual risk:** A same-user process can `chmod` the file and rewrite it between `cordon setup` and the agent reading it. The agent has no mechanism to verify skill integrity before reading — the skill is what tells the agent how to behave, so verification instructions inside the skill can be removed by the attacker. Detection is post-hoc via `cordon doctor`, which requires the human operator to run it. ## R — Repudiation **Threat:** An agent denies having made a specific API call through the proxy. **Mitigation:** Structured logs record route matches, upstream destinations, and request metadata. Credentials and Authorization header values are never logged. Log correlation enables attribution to specific proxy sessions. **Residual risk:** Local logs can be modified by any process with file access. There is no tamper-evident audit chain in the local deployment model. Managed deployments will address this with centralized, immutable logging. ## I — Information Disclosure **Threat:** Credentials leak through logs, memory dumps, debug output, or the agent's context window. **Mitigation:** The `Secret` type has no `Debug` or `Display` impl; attempts to log or format an upstream credential fail at compile time, not at runtime via redaction. Explicit access is required to retrieve the underlying value through token-gated callbacks, making every use visible in code review. `#![forbid(unsafe_code)]` prevents circumventing the type system. Credentials are zeroized on drop. Upstream API credentials never enter the agent's environment — the agent configures a local proxy URL. When token enforcement is active, that proxy URL contains a Cordon token; this token is sensitive local configuration and can be rotated with `cordon token rotate`, but it is not the upstream API credential. **Residual risk:** A memory dump of the running proxy process could capture secrets that have not yet been zeroized. For HTTP routes, the window is bounded by request duration (typically milliseconds). PostgreSQL listener credentials are held while the listener runs. Core dumps should be disabled or restricted in sensitive environments. When using the 1Password backend, credential source tooling introduces an additional side channel. Cordon invokes the `op` CLI, and depending on how the session is authenticated (desktop app biometric, shell-based sign-in, or service account token), other same-user processes may be able to invoke `op` independently and extract secrets from 1Password, bypassing the proxy. This is an inherent property of using an external CLI as a credential backend — Cordon cannot restrict which processes invoke `op`. See the [1Password security considerations](/guides/onepassword#security-considerations) for details on which authentication methods are affected and available mitigations. ## D — Denial of Service **Threat:** A malicious or runaway agent floods the proxy, exhausting resources or upstream API rate limits. **Mitigation:** The proxy is single-tenant (one developer's machine). The blast radius of a denial-of-service is limited to that developer's own API quotas. There is no shared infrastructure to disrupt. **Residual risk:** An agent could exhaust the developer's API rate limits. This is an operational concern, not a security boundary violation. Rate limiting at the proxy layer is a potential future enhancement. ## E — Elevation of Privilege **Threat:** An agent or process accesses credentials for routes it should not have access to. **Mitigation:** Token enforcement limits requests to configured route hosts to clients that present a valid default or client token. Full route matches still decide whether credentials are injected. Route configuration remains the developer's explicit trust decision. **Residual risk:** Default and client tokens are bearer tokens: any process that can read a tokenized proxy URL can trigger credential injection for any configured route in that scope. Client token names support rotation, revocation, cleanup, and logging, but all valid tokens for a scope have the same route access today. There is no per-route policy or per-process identity binding in the local deployment model. Per-caller authorization is planned for managed deployments. ## SSRF attacker model The primary SSRF threat comes from AI agents or LLM-generated code that controls the destination of HTTP requests routed through the proxy. **Attacker capabilities:** * Controls the destination host, port, path, headers, and body * Can make arbitrary HTTP requests through the proxy **High-value targets:** | Target | Risk | | ---------------------------------- | ---------------------- | | Cloud metadata (`169.254.169.254`) | IAM credential theft | | Localhost services | Data exfiltration, RCE | | Private networks (RFC 1918) | Lateral movement | **Defenses:** * Unmatched HTTP destinations have all resolved IPs checked against the denylist before connection * IPv6 transition addresses have embedded IPv4 extracted and re-checked * DNS pinning prevents TOCTOU attacks via DNS rebinding, but is not private-IP blocking * Configured HTTP credential routes bypass private/link-local/loopback denylist checks in v1 as explicit trust decisions by the developer; post-v1 policy work will revisit this default * `denylist_exceptions` entries carve specific `host:port` pairs out of the denylist without credential injection — same operator opt-in trust model as routes, minus credential injection ## Out of scope The following threats are outside the proxy's trust boundary or are managed by other systems. | Threat | Rationale | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Local machine compromise** | Root or admin access can read keychain entries, process memory, and loopback traffic. Same-user processes can connect to the proxy and (on Linux) read keyring entries. The trust boundary is the local operating system. | | **Response data sensitivity** | API responses flow back to the agent in cleartext. Cordon protects credentials, not the data those credentials access. A prompt-injected agent could exfiltrate sensitive response data. This is inherent to any credential proxy model. | | **Supply chain attacks** | Managed by dependency auditing and SAST in CI, not by Cordon's runtime security model. | | **Credential source compromise** | If 1Password or the OS keyring is compromised, the attacker has the secrets regardless of Cordon. When using the `op` CLI backend, the pre-authenticated session may be accessible to other same-user processes depending on the authentication method — this is a property of the CLI-based integration, not a Cordon vulnerability. The proxy trusts the credential source by design; restricting local CLI access is outside its trust boundary. See [1Password security considerations](/guides/onepassword#security-considerations). | # Support Source: https://docs.codezero.io/support How to get help with Cordon. ## Contact support Email [support@codezero.io](mailto:support@codezero.io) for setup help, troubleshooting, licensing questions, or anything that should not be discussed publicly. Developer Edition support is provided on a best-effort basis and does not include guaranteed response times or service-level commitments. For technical issues, include as much of the following as you can: * Cordon version from `cordon --version` * Operating system and install method * The command you ran and the error output you saw * `cordon doctor` output if setup, trust, service, or proxy behavior is failing * A redacted `cordon.toml` if route or listener configuration is involved Do not send secrets, API keys, authorization headers, CA private keys, or unredacted vault item details. ## Public issues For reproducible bugs, documentation fixes, and feature requests that can be discussed publicly, open a GitHub issue: [github.com/codezero-io/cordon/issues](https://github.com/codezero-io/cordon/issues) ## Security reports Do not report vulnerabilities in public issues. Email [security@codezero.io](mailto:security@codezero.io) for security-sensitive reports.