--- url: /cuora/tutorials/first-secret.md --- # Your first secret A full cuora lifecycle — bootstrap, seal, declare, inject, audit — in a throwaway `HOME` so nothing touches your real config. All values below are fake. ## Setup ```sh export HOME="$(mktemp -d)" # scratch HOME for this tutorial export XDG_CONFIG_HOME="$HOME/.config" export XDG_DATA_HOME="$HOME/.local/share" cuora init ``` ```text identity: /tmp/tmp.…/.config/cuora/identity.txt (generated) recipient: age1… vault: /tmp/tmp.…/.local/share/cuora/vault.age ``` On stderr you'll also see the first audit event — every cuora op emits one ndjson line there: ```json {"ts":"2026-01-01T00:00:00Z","actor":"you","op":"init","env":"dev","outcome":"ok"} ``` ## Seal a value ```sh printf '%s' 'sk_test_FAKE000000000000000000' | cuora put cuora://billing/stripe ``` ```text stored cuora://billing/stripe (v1) ``` The plaintext went in on stdin — never an argument. What's on disk is age ciphertext: ```sh file "$XDG_DATA_HOME/cuora/vault.age" # data — not readable ``` ## Declare and inject ```sh cat > cuora.toml <<'EOF' [secrets] STRIPE_KEY = "cuora://billing/stripe" EOF cuora run -- 'echo "key starts with: ${STRIPE_KEY:0:7}…"' ``` ```text key starts with: sk_test… ``` The child saw `$STRIPE_KEY` as a normal env var; nothing else — not your shell, not a file — ever held it. And the audit wire recorded the materialization: ```json {"ts":"…","actor":"you","op":"run","rref":"billing/stripe","env":"dev","outcome":"ok"} ``` ## List without spilling ```sh cuora list # cuora://billing/stripe cuora list --json # [{"ref":"cuora://billing/stripe","env":"dev","version":1,"created_at":"…","rotated_at":null}] ``` `list` is names plus metadata — never values. The only surfaces that print a value are `get` (the escape hatch) and `env` (exports for `eval`). ## Rotate and verify ```sh printf '%s' 'sk_test_FAKE111111111111111111' | cuora rotate cuora://billing/stripe # rotated cuora://billing/stripe (v2) cuora check # config: /tmp/tmp.…/cuora.toml # env: dev → vault "local" # vault "local" (age): readable # secret STRIPE_KEY → cuora://billing/stripe (env dev): resolves # ok ``` `rotate` requires the ref to exist (it's deliberate rotation, not upsert), bumps the version, and stamps `rotated_at`. ## Clean up ```sh rm -rf "$HOME" # the scratch vault and identity go with it ``` ## Where next * [Add a secret](../how-to/add-a-secret.md) — multi-env and pinning. * [Route envs to vaults](../how-to/route-envs-to-vaults.md) — `prod` to a remote vault. * [The audit wire](../reference/audit-events.md) — the ndjson contract. --- --- url: /cuora/how-to/add-a-secret.md --- # Add a secret Store a value under an `cuora://` ref in the vault backing an env. ## The pipe path (scripts and agents) ```sh printf '%s' "$VALUE" | cuora put cuora://ns/name ``` * stdin is read to EOF; **one** trailing newline is stripped, the rest is verbatim (multi-line values work). * Empty input fails — you can't store a blank secret. * The value is never an argument: args land in shell history and `/proc`. Other sources that already hold the value pipe the same way: ```sh pass show stripe/test | cuora put cuora://billing/stripe op read "op://vault/stripe/test" | cuora put cuora://billing/stripe --env dev ``` ## The interactive path (humans) ```sh cuora put cuora://ns/name # stdin is a tty → hidden /dev/tty prompt ``` The prompt doesn't echo — paste the value, hit enter. ## Pick the env Env is resolution *context*, not part of the name — the same `cuora://ns/name` ref can hold different values per env: ```sh printf dev-token | cuora put cuora://billing/stripe --env dev printf prod-token | cuora put cuora://prod/billing/stripe # env pin ``` `cuora://prod/…` pins the env regardless of `--env`/`CUORA_ENV`; the bare form resolves under the selected env. ## Upsert semantics `put` is an upsert: a new ref starts at `version = 1`; re-putting an existing ref replaces the value and bumps the version but is *not* a rotation (`rotated_at` stays untouched — use [`cuora rotate`](rotate-a-secret.md) for deliberate rotations). ## Remove ```sh cuora rm cuora://ns/name ``` ## Verify ```sh cuora list [--env prod] # refs only — never values cuora check # declared [secrets] entries resolve? ``` --- --- url: /cuora/how-to/inject-into-a-process.md --- # Inject secrets into a process `cuora run` is the agent path — resolved values become env vars in the child and nowhere else. `cuora env` is the human path — export lines for `eval`. ## Declare the surface `[secrets]` in `cuora.toml` bounds what `run`/`env` may inject: ```toml [secrets] STRIPE_KEY = "cuora://billing/stripe" DATABASE_URL = "cuora://prod/db/url" # env pin: prod, always ``` Nothing outside this table (plus `-s` ad-hoc pairs) is injectable — least privilege, and `cuora check` verifies every entry resolves. ## `cuora run` — two exec modes ```sh # one arg → bash -c — pipes, redirects, $VAR expansion cuora run -- 'psql "$DATABASE_URL" -c "select 1" | tee out.log' # several args → argv[0] exec'd directly, NO shell — # the program reads its own environment cuora run -- stripe charges list --limit 3 ``` Prefer argv mode when the command has arguments — no quoting puzzles. One caveat: with no shell in the child there is no `$VAR` expansion — write `$STRIPE_KEY` in argv and *your* outer shell eats it (to an empty string) before cuora runs. Use `bash -c` mode when the command line itself needs the value. Exit code is the child's; a child's non-zero exit is not an cuora failure (no `error` audit event). ## Ad-hoc pairs: `-s VAR=cuora://ref` Repeatable, merged over `[secrets]` (same `VAR` — the `-s` pair wins): ```sh cuora run -s TEMP_TOKEN=cuora://ci/token -- ./deploy.sh ``` ## Missing secrets fail closed If any declared or `-s` ref isn't stored in its env, `run` fails before spawning — partial injection is worse than none. ## `cuora env` — the human path ```sh eval "$(cuora env)" # export every declared VAR eval "$(cuora env -s EXTRA=cuora://misc/extra)" # plus ad-hoc pairs cuora env --json # {"VAR": "value"} map ``` Output is `export VAR='…'` with shell-safe quoting for any value. This prints plaintext into your shell environment — that's why it's the *human* path: agents should `run` instead. A `[guardrails]` deny on `env` is the owner's hint. ## The audit trail Each resolved ref emits one materialization event on stderr — `op` is `run` or `env`, `rref` the ref path, `outcome: "ok"`. See [the audit wire](../reference/audit-events.md). --- --- url: /cuora/how-to/rotate-a-secret.md --- # Rotate a secret Replace a stored value deliberately — version bump plus a `rotated_at` timestamp, and an audit event. ```sh printf '%s' "$NEW_VALUE" | cuora rotate cuora://billing/stripe # rotated cuora://billing/stripe (v3) ``` Like `put`, the value arrives on stdin (or a hidden `/dev/tty` prompt) — never an argument. ## `rotate` vs re-`put` | | `put` (upsert) | `rotate` | | --- | --- | --- | | Missing ref | creates it (v1) | **fails** — `NotFound` | | `version` | +1 | +1 | | `rotated_at` | untouched | stamped | Rotation is intentional: a missing ref failing loudly catches a typo'd ref or wrong env that `put` would silently create. ## Rotate in a specific env ```sh printf '%s' "$PROD_VALUE" | cuora rotate cuora://prod/billing/stripe # or: --env prod ``` ## Verify ```sh cuora list --json --env prod # … "version": 3, "rotated_at": "2026-…" ``` Downstream consumers pick the new value up on their next `cuora run` — resolution happens at exec time, nothing caches it. If the value was pushed to a deploy sink, re-push it: [`cuora deploy cuora://billing/stripe`](deploy-to-a-sink.md) — the sink is write-only and doesn't follow the vault on its own. --- --- url: /cuora/how-to/route-envs-to-vaults.md --- # Route envs to vaults Envs are the routing axis: `[envs]` maps each env name to a `[vaults]` backend, so `dev` can stay on the local age file while `prod` reads from a remote ciphertext store. ## The tables ```toml [vaults.local] # the built-in default — entry optional kind = "age" # path / identity / recipients # optional overrides [vaults.cloud] kind = "cf" # self-hosted Worker + D1, age ciphertext url = "https://cuora-vault.you.workers.dev" token_env = "CUORA_CF_TOKEN" # env var NAME — token value stays in env [envs] dev = "local" # bare-name form prod = { vault = "cloud" } # table form ``` Rules: * `local` always exists implicitly (age, default paths) — a `[vaults.local]` entry only overrides it. * Envs not listed route to `local`. `dev`/`staging`/`prod` always exist; `[envs]` keys add custom envs (`ci`, `qa`, …). * `token_env` names the env var carrying the credential — the *name* is in config, the *value* only in the process env. * `CUORA_IDENTITY_FILE` overrides the age identity path globally. ## What changes at run time ```sh cuora list --env prod # opens the "cloud" vault over HTTPS cuora put cuora://prod/x/y # pin → writes to "cloud" cuora run --env prod -- ./app # [secrets] resolve under prod → cloud ``` The ref spelling doesn't change — `cuora://ns/name` is env-agnostic; routing decides which backend serves it. ## The `cf` backend `kind = "cf"` talks to your own Cloudflare Worker fronting D1. All encryption is client-side: the worker stores opaque age ciphertext and has no key material. Setup is a how-to of its own — see [Cloudflare Worker setup](cloudflare-worker-setup.md). ## Verify routing ```sh cuora check # env: dev → vault "local" # vault "local" (age): readable # vault "cloud" (cf): readable ← actually probed over the wire ``` `check` reports every configured and routed vault — including ones no env currently routes to — and fails on routes pointing at undeclared vault names. --- --- url: /cuora/how-to/cached-vaults.md --- # Cache a remote vault locally A remote vault (`kind = "cf"`) costs a network round-trip per read and goes hard-down offline. `[vaults.*] cache` fronts it with a read-through, age-encrypted local mirror — `get`/`list` serve the cache while it's fresh, misses fetch remote and write-through, and a remote failure falls back to the stale copy. ## Turn it on ```toml [vaults.cloud] kind = "cf" url = "https://cuora-vault.you.workers.dev" token_env = "CUORA_CF_TOKEN" cache = ["dev"] # cache only the dev env # cache = true # or: cache every env # cache_ttl_secs = 600 # freshness window, default 300 ``` Three scope forms — `true` for every env, `"dev"` for one, `["dev", "staging"]` for a set. `cache = false` and `cache = []` are the same as leaving it out. ## What lands on disk Rows mirror into `$XDG_CACHE_HOME/cuora/.age` (else `~/.cache/cuora/`) — an ordinary age vault encrypted to the same identity and recipients as the vault itself, so the cache needs no new key material and no new threat surface. Each cached row carries the remote `version`/`rotated_at` verbatim plus a `fetched_at` stamp; a tiny `cloud.listed.json` sidecar holds per-env list markers (env names and timestamps, never refs or values). It's a cache: delete the directory and it rebuilds. ## Freshness model * **Within the TTL** a cached `get`/`list` answers with no network at all — a burst of `cuora run`s costs one round-trip. * **Past the TTL** the read goes remote and rewrites the row. * **Remote unreachable** — the stale copy answers. That's the offline mode the cache exists for, not an error. * **Writes never serve stale**: `put`/`rotate`/`rm` always hit the remote first, then update or invalidate the mirror. A remote `rm` propagates into the cache on the next `list`. The tradeoff to know: within the TTL another writer's change isn't visible — the remote's own change is. Shorten `cache_ttl_secs` if your env rotates often. ## Scope it — prod stays live ```toml [envs] dev = { vault = "cloud" } # cached — fast, offline-capable prod = { vault = "cloud" } # out of scope → every read live-fetches ``` `cache = ["dev"]` means prod rows *never touch the disk mirror* — every prod verb is a straight remote call. A stale prod secret is a footgun; the scope is the safety rail. ## Verify ```sh cuora get cuora://a/b # first call: remote fetch + write-through cuora get cuora://a/b # second call: cache hit, no round-trip ls ~/.cache/cuora/ # cloud.age + cloud.listed.json cuora check # probes the remote like before ``` A cache file that won't decrypt is moved aside to `.corrupt` and rebuilt — corruption degrades to remote-only reads, never to a wrong answer. --- --- url: /cuora/how-to/deploy-to-a-sink.md --- # Deploy a secret to a sink `cuora deploy` pushes a vault-stored value to a write-only target — a `[deploy.NAME]` table in `cuora.toml`. The vault is the source of truth; the sink serves your runtimes directly (e.g. a Worker reading a Secrets Store binding). ## Configure a target ```toml [deploy.prod] kind = "cf-secrets-store" # Cloudflare account Secrets Store account_id = "00000000000000000000000000000000" store_id = "00000000000000000000000000000000" # token_env = "CLOUDFLARE_API_TOKEN" # default; env var NAME only ``` The API token comes from the env var named by `token_env` (default `CLOUDFLARE_API_TOKEN`) — never from the file. ## Push ```sh cuora deploy cuora://billing/stripe --target prod # deployed cuora://billing/stripe → "prod" as dev_billing_stripe (created) ``` * `--target` may be omitted when exactly one `[deploy.*]` table is configured; several targets make it required. * The sink-side name is derived and stable — `env + ref-path` underscored (`dev_billing_stripe`). * Re-deploying updates in place (`updated`, not `created`). ## The direction is one-way Deploy targets are **sinks**, not sources: the plaintext crosses to the sink exactly once, at push time, and nothing ever reads it back through cuora. A deploy emits a `deploy` audit event for the ref. ## Rotate → re-deploy `cuora rotate` changes the vault; the sink doesn't follow on its own. Re-push after rotation: ```sh printf '%s' "$NEW_VALUE" | cuora rotate cuora://prod/billing/stripe cuora deploy cuora://prod/billing/stripe --target prod ``` ## Verify `cuora check` validates deploy tables (kind known, `account_id` + `store_id` present) as findings — it doesn't push anything. --- --- url: /cuora/how-to/guardrails-for-agents.md --- # Guardrails for agents `[guardrails]` is an advisory op table — the owner declaring "agents get these verbs, not those". It runs before the op touches the vault, on every verb. ```toml [guardrails] deny = ["get", "env", "put", "rm", "rotate", "deploy"] ``` ```toml [guardrails] default = "deny" # unlisted ops refused allow = ["run", "list", "check", "manifest", "schema"] ``` * `deny` wins over `allow`; `default` covers the rest (default `"allow"`). * Op names match `cuora manifest`'s `ops` list: `init, put, get, list, rm, rotate, run, env, check, deploy, ui, serve, manifest, schema`. * `manifest`/`schema`/`gen-cli-docs` run before config load — introspection is never gated. ## What a denial looks like ```text cuora: op 'get' denied by guardrails in ./cuora.toml (advisory — remove/relax the rule to allow) ``` …plus an `outcome: "denied"` audit event carrying the requested ref. Exit is non-zero; `--json` emits `{"error": "…"}`. ## Honest scope: advisory, not policy Guardrails are accident prevention, not enforcement: * `CUORA_ACTOR` (and `--actor`) is self-claimed — attribution, not identity. * Nothing stops a determined process from editing `cuora.toml` or running the binary differently. Real policy enforcement is peregrin's job (cuora's orchestrator neighbor); cuora's contract is to *honor* the declared rules and *record* every refusal. If a guardrail denies your op as an agent: don't work around it. Use an allowed verb (`run` instead of `get`), or surface the denial to the human — it's the owner telling you the boundary. ## Typical agent posture Deny every value-egress and mutation verb; allow the inject-and-run path: ```toml [guardrails] default = "deny" allow = ["run", "list", "check", "ui"] ``` The agent can `cuora run` (values materialize inside the child, never in its context) and can list refs — but `get`/`env`, the plaintext printers, are off. --- --- url: /cuora/how-to/cloudflare-worker-setup.md --- # Cloudflare Worker setup The `cf` vault kind is two halves: `cuora-vault-cf` in the CLI (client-side age encryption + blocking HTTP) and a Worker you self-host — a thin store fronting D1 that holds **opaque ciphertext only**. The worker has no key material and cannot read what it serves. ## What you're deploying The worker source, schema, and the authoritative deploy walkthrough live in the repo: * [`crates/cuora-vault-cf/worker/README.md`](https://github.com/9aia/cuora/blob/main/crates/cuora-vault-cf/worker/README.md) — wire protocol (`cuora-vault-cf/2`), `wrangler` steps * `worker/index.js` — the Worker (constant-time auth, scoped tokens, CuoraHmac verification + REST verbs) * `worker/schema.sql` — the D1 table (`name` column added in v2 — `ALTER TABLE secrets ADD COLUMN name TEXT` on an existing db) * `worker/wrangler.toml` — binding config The short version: ```sh cd crates/cuora-vault-cf/worker wrangler d1 create cuora-vault # → paste database_id into wrangler.toml wrangler d1 execute cuora-vault --file=schema.sql wrangler secret put CUORA_VAULT_TOKEN # legacy single bearer, or: wrangler secret put CUORA_VAULT_TOKENS # scoped token map (JSON) — below wrangler deploy ``` Then point a vault at it in `cuora.toml`: ```toml [vaults.cloud] kind = "cf" url = "https://cuora-vault.you.workers.dev" token_env = "CUORA_CF_TOKEN" [envs] prod = { vault = "cloud" } ``` and export the bearer token under the name `token_env` chose (`export CUORA_CF_TOKEN=…`). ## Scoped tokens (wire v2) One static bearer for every env is a big blast radius. Wire v2 scopes tokens per env: the worker reads `CUORA_VAULT_TOKENS`, a JSON map of `token_id → {token, scopes}`, and enforces `{env|"*" → {read, write}}` after auth — a leaked `dev` token can't read `prod`. ```json { "ops/cf-token-dev": { "token": "", "scopes": { "dev": { "read": true, "write": true } } }, "ops/cf-token-viewer": { "token": "", "scopes": { "*": { "read": true, "write": false } } } } ``` On the client side, each scoped credential is an `cuora://` ref — the token itself lives as a secret in the **`local` vault** under the env it scopes; the ref path is the wire `token_id`: ```toml [vaults.cloud.tokens] dev = "cuora://ops/cf-token-dev" prod = "cuora://ops/cf-token-prod" ``` ```sh printf '%s' '' | cuora put cuora://ops/cf-token-dev --env dev printf '%s' '' | cuora put cuora://ops/cf-token-prod --env prod ``` With a scoped token configured the client signs every request — `Authorization: CuoraHmac <token_id>, <ts>, <sig>` where `sig = hex HMAC-SHA256(token, method+path+ts+sha256(body))` and `ts` must be within the worker's 60-second replay window. **The token never travels.** `Bearer` stays accepted during migration, so a mixed fleet upgrades gracefully. **Rotation is `wrangler secret put`** — replace the value inside `CUORA_VAULT_TOKENS`, then `cuora put` the new secret at the token ref. There is deliberately no admin route. ## Private refs (opt-in) By default `ref` and `env` are visible to the store — values are ciphertext, but *which secrets exist* is metadata. With `private_refs = true` the client derives `ref_key = HMAC(identity-derived-key, env + "/" + ref)` and uses it as the storage key; each record also carries a `name` blob — age ciphertext of the real ref — so `list` maps keys back while the store sees only hex: ```toml [vaults.cloud] kind = "cf" url = "…" private_refs = true ``` Existing rows aren't rewritten by the flag alone — run: ```sh cuora migrate --private-refs ``` which re-keys every plaintext-named row (ciphertext moves verbatim, old keys deleted after new ones land). A vault mixing plain and private rows is **mixed-mode**: `list`/`check` fail closed until you migrate — deliberately, since reading the wrong interpretation silently is worse than failing. Turning `private_refs` off again on a private vault fails closed the other way; migrating back is a manual re-put. ## Client-side crypto boundary `put` seals the value with age **before** it leaves the machine; `get` unseals after it arrives. The wire — and the D1 row — carries `{"ciphertext", "name", "version", created_at, rotated_at}`. A compromised worker yields ciphertext and, at most, opaque keys — not values, not ref names (with `private_refs`). Your local `identity.txt` is the only key, same file the local age vault uses; the private-refs derivation is keyed by it, so rotating the identity means a full re-key + migrate. ## Verify ```sh cuora check --env prod # vault "cloud" (cf): readable ← probes the worker over HTTPS ``` An unreachable worker or a bad token shows up as a finding — `check` fails open in the report, never panics. ## Deploy sinks vs the vault Don't confuse the two CF surfaces: * `[vaults.cloud] kind = "cf"` — a *readable* ciphertext store (your Worker + D1). Source of truth. * `[deploy.prod] kind = "cf-secrets-store"` — a *write-only* Cloudflare Secrets Store sink `cuora deploy` pushes plaintext into for Workers to consume via bindings. See [deploy to a sink](deploy-to-a-sink.md). --- --- url: /cuora/reference/cli.md --- # Command-Line Help for `cuora` This document contains the help content for the `cuora` command-line program. **Command Overview:** * [`cuora`↴](#cuora) * [`cuora init`↴](#cuora-init) * [`cuora put`↴](#cuora-put) * [`cuora get`↴](#cuora-get) * [`cuora list`↴](#cuora-list) * [`cuora rm`↴](#cuora-rm) * [`cuora rotate`↴](#cuora-rotate) * [`cuora run`↴](#cuora-run) * [`cuora env`↴](#cuora-env) * [`cuora check`↴](#cuora-check) * [`cuora migrate`↴](#cuora-migrate) * [`cuora deploy`↴](#cuora-deploy) * [`cuora serve`↴](#cuora-serve) * [`cuora manifest`↴](#cuora-manifest) * [`cuora schema`↴](#cuora-schema) ## `cuora` cuora — secrets broker for agents: `cuora://` refs, redaction, env-injection instead of plaintext. Refs name logical secrets (`cuora://billing/stripe`); `--env` picks the resolution context. `[secrets]` in cuora.toml bounds what `run`/`env` may inject. `manifest`/`schema` describe the surface for machines; every verb honors `--json`. **Usage:** `cuora [OPTIONS] ` ###### **Subcommands:** * `init` — Bootstrap: store the age identity in the system keyring when a session secret store is reachable — importing an existing identity.txt rather than orphaning it — else the `~/.config/cuora/identity.txt` keyfile (0600). Verifies the vault opens * `put` — Store a secret. The value comes from stdin (`printf … | cuora put cuora://ns/name`) or a hidden terminal prompt — never args, which land in shell history and `/proc` * `get` — Print a secret's value to stdout — the escape hatch. Prefer `cuora run` so values never enter agent context; guardrails can deny this op * `list` — List the refs stored in the selected env — names only, never values * `rm` — Remove a secret from the selected env * `rotate` — Rotate a secret to a new value (stdin or hidden prompt, like `put`); bumps version + rotated_at * `run` — Run a command with declared secrets injected as env vars * `env` — Print `export VAR='…'` lines for `eval "$(cuora env)"` — the human path (`run` is the agent path). Values print to stdout: `--json` emits a `VAR → value` map * `check` — Validate config + the declared surface: `[envs]` routes point at declared vaults, vaults decrypt, every `[secrets]` ref resolves in its routed env. Exit 1 on findings * `migrate` — Rewrite stored records for a wire/config upgrade. Currently one transform: `--private-refs` re-keys every plaintext-named row in each env routed to a cf vault with `[vaults.*] private_refs = true` — ciphertext moves verbatim, old keys are deleted after the new rows land * `deploy` — Push a stored secret to a write-only deploy target (`[deploy.NAME]` in cuora.toml, e.g. a CF Secrets Store). The vault is the source of truth; the sink never gives the value back. With one configured target, `--target` may be omitted * `serve` — Serve the Vault port over ndjson — one JSON request per stdin line, one response per stdout line, audit events on stderr unchanged. For orchestrators embedding cuora (peregrin); with `[serve.pdp]` configured, a policy decision point is consulted before every vault-touching op. `{"op":"manifest"}` on the wire describes the protocol * `manifest` — Capabilities, JSON shapes, env vars, audit contract — agent self-discovery (always JSON) * `schema` — JSON Schema for `cuora.toml` ###### **Options:** * `--json` — Emit JSON — stable machine shapes on every verb; errors are `{"error": "…"}` on stderr with a non-zero exit * `--env ` — Env context — `dev`/`staging`/`prod`/custom. Overrides `CUORA_ENV`; default `dev` * `--config ` — Config file, overriding the search ($CUORA_CONFIG → ./cuora.toml → ~/.config/cuora/cuora.toml) * `--actor ` — Actor attribution for audit events (default $CUORA_ACTOR, else $USER) ## `cuora init` Bootstrap: store the age identity in the system keyring when a session secret store is reachable — importing an existing identity.txt rather than orphaning it — else the `~/.config/cuora/identity.txt` keyfile (0600). Verifies the vault opens **Usage:** `cuora init [OPTIONS]` ###### **Options:** * `--with-file` — Force the identity keyfile even when a keyring is available — the headless/CI mode ## `cuora put` Store a secret. The value comes from stdin (`printf … | cuora put cuora://ns/name`) or a hidden terminal prompt — never args, which land in shell history and `/proc` **Usage:** `cuora put ` ###### **Arguments:** * `` — `cuora://` ref, e.g. cuora://billing/stripe ## `cuora get` Print a secret's value to stdout — the escape hatch. Prefer `cuora run` so values never enter agent context; guardrails can deny this op **Usage:** `cuora get ` ###### **Arguments:** * `` — `cuora://` ref ## `cuora list` List the refs stored in the selected env — names only, never values **Usage:** `cuora list` ## `cuora rm` Remove a secret from the selected env **Usage:** `cuora rm ` ###### **Arguments:** * `` — `cuora://` ref ## `cuora rotate` Rotate a secret to a new value (stdin or hidden prompt, like `put`); bumps version + rotated_at **Usage:** `cuora rotate ` ###### **Arguments:** * `` — `cuora://` ref ## `cuora run` Run a command with declared secrets injected as env vars. Injection surface = `[secrets]` in cuora.toml plus `-s VAR=cuora://ref` ad-hoc pairs; nothing else is injectable. Two exec modes after `--`: `cuora run -- 'pnpm test | tee log'` — one string → `bash -c` `cuora run -- pnpm test --filter x` — argv → direct exec argv mode needs no shell quoting — prefer it when the command has arguments. Exit code is the child's. **Usage:** `cuora run [OPTIONS] -- ...` ###### **Arguments:** * `` — Command to run (after `--`). One arg → `bash -c`; several → `argv[0]` exec'd directly with the rest ###### **Options:** * `-s`, `--secret ` — Extra `VAR=cuora://ref` pairs to inject (refused when `[guardrails] deny_adhoc` is set) ## `cuora env` Print `export VAR='…'` lines for `eval "$(cuora env)"` — the human path (`run` is the agent path). Values print to stdout: `--json` emits a `VAR → value` map **Usage:** `cuora env [OPTIONS]` ###### **Options:** * `-s`, `--secret ` — Extra `VAR=cuora://ref` pairs to resolve ## `cuora check` Validate config + the declared surface: `[envs]` routes point at declared vaults, vaults decrypt, every `[secrets]` ref resolves in its routed env. Exit 1 on findings **Usage:** `cuora check` ## `cuora migrate` Rewrite stored records for a wire/config upgrade. Currently one transform: `--private-refs` re-keys every plaintext-named row in each env routed to a cf vault with `[vaults.*] private_refs = true` — ciphertext moves verbatim, old keys are deleted after the new rows land **Usage:** `cuora migrate [OPTIONS]` ###### **Options:** * `--private-refs` — Re-key cf-vault rows under HMAC-derived storage keys, attaching the encrypted `name` blob (P-004) ## `cuora deploy` Push a stored secret to a write-only deploy target (`[deploy.NAME]` in cuora.toml, e.g. a CF Secrets Store). The vault is the source of truth; the sink never gives the value back. With one configured target, `--target` may be omitted **Usage:** `cuora deploy [OPTIONS] ` ###### **Arguments:** * `` — `cuora://` ref to push ###### **Options:** * `--target ` — Deploy target name (`[deploy.NAME]`); required when several are configured ## `cuora serve` Serve the Vault port over ndjson — one JSON request per stdin line, one response per stdout line, audit events on stderr unchanged. For orchestrators embedding cuora (peregrin); with `[serve.pdp]` configured, a policy decision point is consulted before every vault-touching op. `{"op":"manifest"}` on the wire describes the protocol **Usage:** `cuora serve` ## `cuora manifest` Capabilities, JSON shapes, env vars, audit contract — agent self-discovery (always JSON) **Usage:** `cuora manifest` ## `cuora schema` JSON Schema for `cuora.toml` **Usage:** `cuora schema` This document was generated automatically by clap-markdown. --- --- url: /cuora/reference/config.md --- # Config — `cuora.toml` ## Top level | field | type | required | notes | |---|---|---|---| | `vaults` | `{name: vault}` | optional | Named vault backends. `local` always exists implicitly (age, default paths); an entry only overrides it or adds more. | | `envs` | `{env: vault|{vault}}` | optional | Env → vault routing. Envs not listed route to `local`; `dev`/`staging`/`prod` always exist. | | `secrets` | `{VAR: cuora://ref}` | optional | The declared secret surface: env-var name → `cuora://` ref. Bounds what `run`/`env` may inject — least privilege, no ambient vault dump. | | `deploy` | `{name: target}` | optional | Named deploy targets — write-only sinks (`cuora deploy` pushes a stored value in; nothing ever reads one back). | | `guardrails` | `table` | optional | Advisory op guardrails — accident prevention for standalone use, honestly *not* a policy boundary (a locally claimed actor is spoofable by construction). | | `serve` | `table` | optional | The `cuora serve` surface — ndjson request/response on stdio plus the optional policy decision point. Absent, serve runs with no PDP — same advisory posture as the single-shot CLI. | | `identity` | `IdentityConfig` | optional | Which backend holds the age identity — `[identity]`. Absent → `keyring` (fail closed; `auto`/`file` opt out). | ## `[vaults.NAME]` — a named vault backend | field | type | required | notes | |---|---|---|---| | `kind` | `string` | optional | Backend kind — `"age"` (built-in, local file) or `"cf"` (self-hosted Cloudflare Worker + D1, `cuora-vault-cf`). | | `path` | `string` | optional | Vault file path override (age impl; `~` is *not* expanded — write the path in full). | | `identity` | `string` | optional | Age identity file override (`CUORA_IDENTITY_FILE` wins over this; see `cuora-ops`). `cf` vaults decrypt with the same keyfile — all vault impls store age ciphertext (ADR-0001). | | `recipients` | `list` | optional | Extra age recipients to encrypt to, beyond the identity's public key (`age1…` strings). | | `url` | `string` | optional | Worker base URL (`cf` impl), e.g. `https://cuora-vault.you.workers.dev`. | | `token_env` | `string` | optional | Name of the env var holding the bearer token (`cf` impl) — the token *name* lives in config, the token *value* only in the process env. | | `tokens` | `BTreeMap` | optional | Per-env scoped credentials (`cf` impl, P-003): env → the `cuora://` ref of a token stored in the `local` vault. Each resolved token authenticates as `CuoraHmac <ref-path>, ts, nonce, sig` — the ref path is the wire `token_id`. Token *refs* live in config, never token values. | | `private_refs` | `bool` | optional | Opt-in private ref metadata (`cf` impl, P-004): storage keys become HMAC-derived hex and every record carries an age-encrypted `name` blob, so the remote store can't see which secrets exist. Default false; `cuora migrate --private-refs` rewrites existing rows. | | `cache` | `CacheScope` | optional | Read-through encrypted local cache in front of this vault (P-007): `true` caches every env; a name or list scopes it — `cache = "dev"` or `cache = ["dev", "staging"]`. Rows land at `$XDG_CACHE_HOME/cuora/<name>.age` as ordinary age ciphertext carrying the remote `version`/`rotated_at` verbatim; freshness is TTL-bounded (`cache_ttl_secs`) and writes are always remote-first. Meant for remote backends — `dev` may cache, `prod` should live-fetch. | | `cache_ttl_secs` | `int` | optional | Cache freshness window in seconds — a cached row or env listing younger than this answers without a remote round-trip; past it the remote is re-fetched and the cache updated. A remote *failure* falls back to the stale copy — the offline mode. Absent → 300. | ## `[envs]` — env → vault routing (table form) | field | type | required | notes | |---|---|---|---| | `vault` | `string` | required | Vault name in `[vaults]` (or the implicit `local`). | ## `[deploy.NAME]` — a write-only push target | field | type | required | notes | |---|---|---|---| | `kind` | `string` | optional | Target kind — `"cf-secrets-store"` (Cloudflare account Secrets Store, `cuora-deploy-cf`). | | `account_id` | `string` | optional | Cloudflare account id (`cf-secrets-store`). | | `store_id` | `string` | optional | Secrets Store id (`cf-secrets-store`). | | `token_env` | `string` | optional | Name of the env var holding the API token — never the token itself (`cf-secrets-store` defaults to `CLOUDFLARE_API_TOKEN`). | | `base_url` | `string` | optional | API base URL override — for tests, gateways, and self-hosted proxies. Defaults to `https://api.cloudflare.com/client/v4`. | ## `[guardrails]` — advisory op table | field | type | required | notes | |---|---|---|---| | `default` | `"allow"|"deny"` | optional | Fallback for ops in neither list. | | `allow` | `op list` | optional | Ops always allowed. | | `deny` | `op list` | optional | Ops always refused — deny wins over allow. | | `deny_adhoc` | `bool` | optional | Refuse `-s VAR=cuora://ref` ad-hoc injection on `run`/`env` (MED-5). Without it, a posture like `deny = ["get"]` is sidestepped: `cuora run -s V=cuora://x -- printenv V` *is* a get. Declared `[secrets]` injection is unaffected — the declared surface is auditable config, not an ad-hoc grab. | ## `[serve]` — the `cuora serve` surface | field | type | required | notes | |---|---|---|---| | `pdp` | `table` | optional | The external policy decision point serve consults before every op that can materialize (`get`/`put`/`rm`/`rotate`/ `list`/`check`). Absent, no consult happens — the advisory posture only. | ## `[serve.pdp]` — the external policy decision point | field | type | required | notes | |---|---|---|---| | `exec` | `string` | required | The exec driver command — run via `sh -c` once per decision, request JSON on stdin, verdict JSON on stdout. **Trust root**: the line authorizes shell exec; the child inherits only the `cuora-shell` env allowlist. `cuora serve` warns when this arrives via an implicit `./cuora.toml` — pin config with `--config`/`$CUORA_CONFIG`. | | `allow_on_error` | `bool` | optional | What an unreachable PDP means. `false` (default) fails **closed** — the op is denied; a policy engine that can't answer must not silently mean "allowed". `true` fails open — the op runs anyway and the audit event records the degraded verdict. | | `timeout_ms` | `int` | optional | Per-decision deadline in milliseconds — the child is killed and the consult counts as *unreachable* past it. A hung or flooding PDP must never freeze the serve loop; absent → 5000. | ## `[envs]` route forms Either a bare vault name or the table above: ```toml [envs] dev = "local" prod = { vault = "cloud" } ``` ## Constraints * `deny_unknown_fields`: typos in field names fail the load. * Search order: `--config` → `$CUORA_CONFIG` → `./cuora.toml` → `~/.config/cuora/cuora.toml`. The file is optional — no `cuora.toml` anywhere just means defaults (local age vault, `dev` env). * Token values never live in the file — `token_env` fields name the env var that carries them. * `cuora schema` emits the JSON Schema for this shape; `cuora check` validates a real file end-to-end. --- --- url: /cuora/reference/audit-events.md --- # Audit events The audit wire: one ndjson line on **stderr** per op outcome, plus an OTLP span per event when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. cuora keeps no local ledger (ADR-0001 — aggregation is peregrin's); the stderr stream *is* the human contract and the OTLP stream the machine one, so a wrapper — peregrin, sepia, your own harness — can capture either, and a human sees a breadcrumb. ## Shape ```json {"ts":"2026-01-01T00:00:00Z","actor":"devin","op":"run","rref":"billing/stripe","env":"dev","outcome":"ok"} ``` | field | notes | | --- | --- | | `ts` | RFC 3339, UTC | | `actor` | `--actor` → `CUORA_ACTOR` → `$USER`/`$LOGNAME` → `anon`. Self-claimed — attribution, not identity | | `op` | the verb: `init put get list rm rotate run env check deploy ui serve manifest schema` | | `rref` | the resolved ref path, env pin stripped — same on `denied`/`error` when the arg parses, else the raw arg verbatim. **Absent** on op-scoped events | | `env` | the env the op ran under | | `outcome` | `ok` | `denied` | `error` | | `policy` | **serve-path only, optional** — the PDP verdict `{allow, reason?, obligations?}` behind the outcome; absent on every other path | ## When events fire | event | meaning | | --- | --- | | `ok`, ref-scoped | one **per ref** materialized or mutated — `put`, `get`, `rotate`, `rm`, `deploy`, and per resolved ref inside `run`/`env` | | `ok`, op-scoped | ref-less verb completed — `init`, `list`, `check`, `ui`, `serve` | | `denied` | `[guardrails]` — or a serve-path PDP verdict of `allow: false` (including fail-closed on an unreachable PDP) — refused the op before it touched the vault; `rref` is the requested ref (normalized when parseable) | | `error` | the op ran and failed — missing secret, vault i/o, bad ref; a `check` with findings also audits `error` (it exits 1); also emitted when config load itself fails (best-effort actor/env from flags + process env) | Two deliberate silences: * `manifest`, `schema`, `gen-cli-docs` run before config load — pure introspection, no event. * A `run` child's non-zero exit is the *child's* outcome — no `error` event for it. cuora's own exit code is the child's. ## `serve` and the `policy` field `cuora serve` emits the same events — `get`/`put`/`rm`/`rotate` requests audit exactly like the CLI verbs, under the request's claimed `actor` when it carries one. The PDP consult covers the ops that can materialize — `get`/`put`/`rm`/`rotate` *and* `list`/`check` (record decrypts, vault probes, scoped-token gets) — so a `check`'s incidental token `get`s ride the same `policy`-bearing events. On the serve path only, every event a consulted `[serve.pdp]` gated gains a `policy` object — `{allow, reason?, obligations?}` — so the stream shows the verdict behind the outcome: a PDP allow, a PDP deny (`outcome: "denied"`, `policy.allow: false`), or a degraded allow recorded when `allow_on_error` overrode an unreachable PDP. The `serve` verb itself closes with one op-scoped `ok`. See [the serve protocol](serve-protocol.md). ## `ui` and the TUI `cuora ui` emits one op-scoped `ok` when the browser exits cleanly. Inside it, browsing is silent — listing refs and metadata is not a materialization, and **live stderr audit is muted for the whole session** so events can't splatter over the alternate screen; everything held replays to stderr after the terminal is restored. The `r`→`r` reveal goes through the audited `get` path: a real `{"op":"get","rref":…,"outcome":"ok"}` event per value shown — held like the rest, replayed on exit. ## OTLP export Set `OTEL_EXPORTER_OTLP_ENDPOINT` (e.g. `http://localhost:4318`) and every audit event is additionally buffered as an `cuora.` span — `ExportTraceServiceRequest` JSON posted to `/v1/traces` on flush. Span attributes mirror the ndjson fields: `cuora.op`, `cuora.rref` (when present), `cuora.env`, `cuora.actor`, `cuora.outcome`, and `cuora.policy.*` on serve-path events; `Outcome::Error` marks the span `STATUS_CODE_ERROR`. `service.name` comes from `OTEL_SERVICE_NAME` (default `cuora`). Flush timing: CLI verbs export once at exit; `cuora serve` flushes per request (it's long-running); the TUI flushes at exit like any verb. Related env vars: `OTEL_EXPORTER_OTLP_HEADERS` (`k=v` pairs, percent-decoded), `OTEL_EXPORTER_OTLP_TIMEOUT` (ms, default 10000\), `OTEL_SDK_DISABLED=true` turns export off entirely, and `CUORA_AUDIT_STDERR=0` silences the stderr wire (OTLP-only mode). ## Failure posture Audit emission is best-effort on both wires: a closed stderr (broken pipe) never takes the broker down — the write error is swallowed by design — and a failed OTLP export warns once on stderr, never fails the op. Note: stderr can also carry non-ndjson `cuora:` *notice* lines (permission warnings, keyring fallback notes) — UX chatter, not audit wire. Consumers parsing stderr as pure ndjson should tolerate lines that don't start with `{`. During `cuora ui` both audit events and notices are held and replayed after the terminal is restored. ## Granularity Per-ref events fire when a value crosses the store boundary toward a caller: `get`, `put`, `rotate`, `rm`, `deploy`, `run`, `env`, and serve `get`/`put`/`rotate`/`rm`. Bulk reads — `list`, `check`, the TUI reload, serve `list` — emit **op-scoped** events only: they decrypt records internally to verify or enumerate, but no value is returned to the caller, so there is no per-ref materialization to attribute. If your threat model counts "decrypted in-process" as materialization, treat bulk ops as single events covering N refs — the op name tells you which verb ran, not which refs it touched. --- --- url: /cuora/reference/serve-protocol.md --- # The `cuora serve` protocol `cuora serve` is the Vault port over newline-delimited JSON — the surface orchestrators (peregrin) embed. One JSON request per **stdin** line, one JSON response per **stdout** line, and the [audit wire](audit-events.md) stays on **stderr**, unchanged — three separate fds, so a reply never rides the event stream and an event never answers a request. Protocol version: `cuora-serve/1` — send `{"op":"manifest"}` on the wire for the machine-readable self-description. Serve is a thin front over the same `cuora-ops` plumbing the CLI uses: `[envs]` routing, `cuora:///…` pins, and audit events are identical. `run`/`env`/`deploy`/`init`/`ui`/`migrate`/`schema` stay single-shot CLI verbs — serve covers what an embedder needs in-process. ## Requests ```json {"id": 1, "op": "get", "ref": "cuora://billing/stripe", "env": "prod", "actor": "agent:peregrin", "attestation": "…"} ``` | field | notes | | --- | --- | | `id` | any JSON value — echoed verbatim (absent → `null`), ≤ 4 KiB serialized | | `op` | `get` | `put` | `rm` | `rotate` | `list` | `check` | `manifest` | `shutdown` | | `ref` | `cuora://ns/name` ≤ 1 KiB — required by the four vault ops; an `cuora:///…` pin overrides `env` | | `env` | per-request env override — else the loaded context (`--env`/`CUORA_ENV`) | | `value` | plaintext ≤ 64 KiB — required by `put`/`rotate`. Values cross this wire deliberately: the parent owns the channel exactly like it owns `cuora get`'s stdout | | `actor` | claimed actor id ≤ 256 B — advisory attribution recorded on audit events; validated, never verified | | `attestation` | opaque identity claim ≤ 64 KiB — passed to the PDP verbatim, never interpreted by cuora | Hard bounds on the wire: a request line may be at most **4 MiB** — an oversized line answers `bad_request` and the reader resyncs to the next line, so a broken embedder can't exhaust memory. Blank or whitespace-only lines get **no reply** — legal keep-alive, unanswered. A non-UTF-8 line is a `bad_request`, not a crash. ## Responses ```json {"id": 1, "ok": true, "result": {"ref": "cuora://billing/stripe", "env": "prod", "value": "…", "version": 3, "created_at": "…", "rotated_at": "…"}, "policy": {"allow": true, "reason": "…", "obligations": ["…"]}} ``` ```json {"id": 2, "ok": false, "error": {"code": "denied", "message": "denied by policy: no prod reads"}} ``` `policy` appears on a response whenever a PDP was consulted for that op — the same verdict the audit event records. | error code | meaning | | --- | --- | | `bad_request` | malformed/oversized/non-UTF-8 line, missing `ref`/`value`, invalid ref/env/actor, field over cap — the loop keeps serving | | `unknown_op` | op not in the table (a real CLI verb reports "not served — use the single-shot CLI") | | `denied` | `[guardrails]` or PDP refusal | | `pdp_unreachable` | the PDP couldn't answer (spawn, non-zero exit, timeout, flooded/oversized/unparseable verdict) and `allow_on_error` is false — fail-closed | | `not_found` | the ref isn't stored in that env | | `op_error` | vault/config/io failure | `shutdown` replies then exits cleanly; EOF on stdin exits too. `manifest` and `shutdown` are protocol meta-ops: they skip guardrails and the PDP consult by design — gating session lifecycle is the embedder's spawn decision, not a materialization. ## The policy decision point — `[serve.pdp]` Configured, serve consults the PDP **before every op that can materialize** — `get`/`put`/`rm`/`rotate`, and `list`/`check` too: `list` decrypts every record's metadata and `check` probes vaults, including scoped-token `get`s and outbound signed HTTP against cf vaults. Nothing crosses the vault boundary without a verdict. `manifest`/`shutdown` are meta-ops and don't consult. `[guardrails]` still apply on the serve path (the advisory layer, checked before the consult), and request validation runs before the consult too — a doomed request never spawns a PDP. ```toml [serve.pdp] exec = "peregrin policy decide" # sh -c per decision allow_on_error = false # default: fail closed timeout_ms = 5000 # default: 5s per consult ``` A `[serve.pdp] exec` line is **trust-root config**: it authorizes shell execution. When `cuora serve` loads it from an *implicit* `./cuora.toml` (the search path, not a pinned `--config`/`$CUORA_CONFIG` or `~/.config/cuora/`), it warns on stderr — the working directory decides what code runs. The `exec` command runs once per decision — request JSON on stdin, verdict JSON on stdout, stderr discarded. The child inherits only the `cuora-shell` env allowlist (same scrub `cuora run` applies): `CUORA_CF_TOKEN`, `CLOUDFLARE_API_TOKEN`, and every other parent credential stay out of the decider. ```json {"actor": "agent:peregrin", "attestation": "…", "op": "get", "env": "prod", "ref": "billing/stripe"} ``` `ref` is the resolved path with the env pin stripped — and is **absent** on `list`/`check` consults, which carry no ref. ```json {"allow": true, "reason": "…", "obligations": ["redact", "session-bound"]} ``` `allow` is required; `reason` and `obligations` are free-form context the PDP wants on record (allow-with-conditions — cuora records them; enforcing them is the requester's job). A deny is a `denied` response plus a `denied` audit event — no vault access. A consult is hard-bounded so a misbehaving PDP can't take the broker with it: the child is killed at `timeout_ms`, the verdict is capped at 64 KiB (a flooding child blocks on its own pipe and dies on the deadline — it cannot sink memory), and the request write can't deadlock against a child that never reads stdin. Spawn failure, non-zero exit, timeout, an oversized or unparseable verdict is **unreachable** → the op is denied (`pdp_unreachable`) unless `allow_on_error = true`, which runs the op anyway and records the degraded verdict (`policy.reason` says why). Fail-closed is the default because an unanswered policy engine must not silently mean "allowed". Why `exec` and not a socket or HTTP: zero new dependencies, sync only, no daemon lifecycle. A PDP that wants a persistent endpoint can put `socat`/`nc`/`curl` in its own exec line — the wire stays the same. Note the per-op cost: every consult spawns a fresh `sh -c`, `list` decrypts each record, and `check` probes every vault — batch reads rather than looping `list`. ## Actor claims are advisory `actor` and `attestation` are *claims*, not proof — cuora validates the actor string's shape and passes both through to audit and the PDP. Signed, short-lived attestation is the PDP's side (peregrin's design, a later decision); ADR-0003 only fixes the pass-through seam. --- --- url: /cuora/reference/env-vars.md --- # Environment variables Process env vars cuora itself reads. (The vars in `[secrets]` are yours — this is cuora's own surface; `cuora manifest` reports the same table.) | var | default | used for | | --- | --- | --- | | `CUORA_CONFIG` | — | config file override, between `--config` and `./cuora.toml` in the search order | | `CUORA_ENV` | `dev` | env context — resolution + routing (`--env` wins) | | `CUORA_ACTOR` | `$USER` → `anon` | audit attribution (`--actor` wins). Self-claimed | | `CUORA_IDENTITY_FILE` | `~/.config/cuora/identity.txt` | age identity keyfile for every vault impl — pins file mode outright | | `CUORA_IDENTITY_BACKEND` | `keyring` | `keyring` (default — fail closed, no file fallback) | `auto` (keyring → keyfile fallback, for headless) | `file` (never probe the keyring); wins over `[identity] backend` | | `CUORA_CF_TOKEN` | — | bearer token for `kind = "cf"` vaults — *default* name; `[vaults.*].token_env` picks another | | `CLOUDFLARE_API_TOKEN` | — | API token for `cf-secrets-store` deploy sinks — *default* name; `[deploy.*].token_env` picks another | | `CUORA_AUDIT_STDERR` | on | `0`/`false`/`off`/`no` silences the stderr ndjson audit wire — OTLP-only mode | | `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OTLP/HTTP collector base — audit events export as `cuora.` spans to `/v1/traces`; `http://` is accepted only for loopback collectors (`localhost`, `127.0.0.1`, `::1`) — same policy as cf vault URLs | | `OTEL_EXPORTER_OTLP_HEADERS` | — | `k=v,k2=v2` auth headers for the OTLP endpoint (values percent-decoded) | | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | export timeout in ms | | `OTEL_SERVICE_NAME` | `cuora` | `service.name` resource attribute on exported spans | | `OTEL_SDK_DISABLED` | — | `true`/`1` disables OTLP export entirely | Honored XDG paths (when the matching override is unset): | var | falls back to | | --- | --- | | `XDG_CONFIG_HOME` | `~/.config` — identity + user-level `cuora.toml` | | `XDG_DATA_HOME` | `~/.local/share` — the default `vault.age` | Two rules of thumb: * `token_env` config fields name env vars — the token *value* only ever lives in the process env. * `CUORA_ACTOR` is a self-claimed string for the audit trail, not authentication; see the [threat model](../explanation/threat-model.md). --- --- url: /cuora/reference/crate-api.md --- # Crate API Rustdoc for every workspace crate — public APIs are documented at source (doc comments). * [cuora-core](/cuora/api/cuora_core/index.html) — domain * [cuora-config](/cuora/api/cuora_config/index.html) — port * [cuora-deploy](/cuora/api/cuora_deploy/index.html) — port * [cuora-vault](/cuora/api/cuora_vault/index.html) — port * [cuora-ops](/cuora/api/cuora_ops/index.html) — ops * [cuora-config-toml](/cuora/api/cuora_config_toml/index.html) — adapter * [cuora-deploy-cf](/cuora/api/cuora_deploy_cf/index.html) — adapter * [cuora-driver](/cuora/api/cuora_driver/index.html) — adapter * [cuora-identity](/cuora/api/cuora_identity/index.html) — adapter * [cuora-identity-file](/cuora/api/cuora_identity_file/index.html) — adapter * [cuora-identity-keyring](/cuora/api/cuora_identity_keyring/index.html) — adapter * [cuora-notice](/cuora/api/cuora_notice/index.html) — adapter * [cuora-serve](/cuora/api/cuora_serve/index.html) — adapter * [cuora-shell](/cuora/api/cuora_shell/index.html) — adapter * [cuora-testkit](/cuora/api/cuora_testkit/index.html) — adapter * [cuora-vault-age](/cuora/api/cuora_vault_age/index.html) — adapter * [cuora-vault-cached](/cuora/api/cuora_vault_cached/index.html) — adapter * [cuora-vault-cf](/cuora/api/cuora_vault_cf/index.html) — adapter * [cuora](/cuora/api/cuora/index.html) — binary --- --- url: /cuora/explanation/architecture.md --- # Architecture Ports-and-adapters over a flat `crates/` workspace. `cuora-core` is pure domain — `cuora://` refs, the redacting `Secret` newtype, the env axis, actor attribution — with no I/O. Ports are traits (`cuora-config`, `cuora-vault`, `cuora-deploy`); impls live in separate crates; `crates/cuora-cli` (package `cuora`) is the composition root that wires impls to verbs. Design rules: * **No `unsafe`, no async** — the binary stays dependency-light and synchronous; vault impls that speak HTTP use a blocking client (`ureq`), so no runtime embeds anywhere. * **`Secret` never exposes plaintext** — no `Display`/`Debug` of the value; `.expose()` is the only way out, called at edges only (child env injection, `cuora env` exports, `get`). * **Stores hold ciphertext only** — every vault impl encrypts client-side with age (ADR-0001); a remote vault is ciphertext over the wire *and* at rest, never a plaintext service. * **Ports are conformance-tested** — `cuora-testkit` runs the same contract suite against every vault impl (`cuora-vault-age`, `cuora-vault-cf`): upsert/rotate/list/env-isolation semantics can't drift per backend. * **One ops shell** — `cuora-ops` owns config load, env→vault routing, the env-pin rule, and audit emission; the CLI and the TUI are both thin front-ends over it and cannot diverge. | crate | layer | purpose | |---|---|---| | `cuora-core` | domain | cuora domain core: cuora:// refs, redacting Secret, env axis, actor, config model — pure, no I/O | | `cuora-config` | port | Config port: where cuora.toml-shaped config comes from | | `cuora-deploy` | port | Deploy port: write-only push targets (sinks) for secret values — never a source | | `cuora-vault` | port | Vault port: readable secret stores — get/put/rm/list/rotate, per-env; every impl stores opaque ciphertext | | `cuora-ops` | ops | cuora ops I/O: config load, env/ref resolution, vault open, verb plumbing, audit emission | | `cuora-config-toml` | adapter | Config impl: cuora.toml file (explicit path, env, ./, ~/.config) | | `cuora-deploy-cf` | adapter | Deploy impl: push secret values to a Cloudflare account Secrets Store (write-only sink) via the CF API v4 | | `cuora-driver` | adapter | tamias-driver-cuora — pavo view driver over cuora vault metadata (read-only; never renders secret values) | | `cuora-identity` | adapter | Identity port: where the age identity (master key) comes from — file, keyring, later sealed variants | | `cuora-identity-file` | adapter | Identity impl: the ~/.config/cuora/identity.txt age keyfile (0600) — the headless/CI default | | `cuora-identity-keyring` | adapter | Identity impl: the platform session secret store (secret-service / Keychain / Credential Manager) via keyring-core | | `cuora-notice` | adapter | Process-wide UX notices — cuora:-prefixed stderr notes, mutable for terminal-owning surfaces | | `cuora-serve` | adapter | cuora serve — the ndjson request/response IPC surface orchestrators embed, with the external policy-decision consult (P-005) | | `cuora-shell` | adapter | Run a command in a working directory via bash -c — the generic shell-out helper | | `cuora-testkit` | adapter | Vault conformance suite — every cuora-vault impl passes the same contract tests (fake values only) | | `cuora-vault-age` | adapter | Local vault impl: one age-x25519-encrypted JSON doc — the v1 default backend | | `cuora-vault-cached` | adapter | Composite vault impl: read-through age-encrypted local cache in front of any backend (P-007) | | `cuora-vault-cf` | adapter | Remote vault impl: self-hosted Cloudflare Worker + D1 holding per-secret age ciphertext — the Worker never sees plaintext | | `cuora` | binary | cuora — secrets broker for agents: reference URIs, redaction, env-injection CLI | ## Front-ends * **`cuora` (CLI)** — clap verbs + `--json`/`manifest`/`schema` for machines; the agent interface. * **`cuora ui` (TUI)** — a read-only ratatui browser over the same ops; feature-gated (`--no-default-features` for a lean binary). * **`cuora serve`** — ndjson IPC over the Vault port with an exec-driven `[serve.pdp]` policy-decision hook (ADR-0003); peregrin's embedding point. ## Boundary — secret materialization only cuora owns refs, vaults, env injection, redaction, and the audit wire. Neighbors own theirs — see [boundaries](boundaries.md) for the full map. --- --- url: /cuora/explanation/threat-model.md --- # Threat model What cuora protects against, honestly — and what it explicitly does not. ## Protects against * **Plaintext in the repo** — code and config carry `cuora://` refs, never values. There is nothing to leak in a commit, a diff, a paste, or a prompt context. * **Plaintext in shell history / process listings** — values never enter via CLI arguments (args land in `~/.bash_history` and `/proc/*/cmdline`). `put`/`rotate` read stdin or a hidden `/dev/tty` prompt. * **Plaintext in agent context** — `cuora run` injects values into the child's environment; the agent that spawned it sees the child's *output*, never the variable. `[secrets]` bounds the injectable surface so a `run` can't dump the whole vault. * **Accidental printing** — `Secret` has no `Display`/`Debug` of the value (`.expose()` only), `list` is names + metadata only, and the TUI's posture is browse-never-spill (reveal is a deliberate two-step, audited). * **Ciphertext at rest, everywhere** — every vault impl stores age ciphertext. The local file is unreadable without the identity; the `cf` remote store holds ciphertext over the wire *and* in D1 — a compromised worker yields blobs, not values. With `[vaults.*] private_refs = true` it can't even see *which* secrets exist: storage keys are HMAC-derived hex and the real ref travels in an age-encrypted `name` blob. * **Token leakage on the cf wire** — scoped tokens (`CUORA_VAULT_TOKENS`) bound a leaked credential to its `{env, read, write}` scope instead of every env forever, and CuoraHmac request signing means the token never travels at all (a ±60s window bounds replay). All auth compares are constant-time. * **Silent misuse** — every materialization, denial, and error emits an ndjson audit event on stderr, so capture is a wrapper away. `check` fails closed on a vault that won't decrypt, and on a mixed-mode cf vault (private config + plaintext rows). ## Does NOT protect against * **A determined local actor** — `CUORA_ACTOR` is self-claimed (attribution, not identity) and `[guardrails]` are advisory: anyone who can edit `cuora.toml` or run the binary can bypass them. They prevent *accidents*, not adversaries. Real multi-actor policy and enforcement belong to **peregrin**. * **The identity's host protection** — the master key lives in the session keyring by default (ADR-0002), which protects it about as well as your login session — and the default backend fails closed rather than silently downgrading. The `0600` keyfile (`identity.txt`, raw key material like an unencrypted SSH key) is reached only via explicit opt-in: `backend = "auto"`/`"file"`, `CUORA_IDENTITY_FILE`, or `[vaults.*].identity` — the headless/CI path, where no session store exists anyway. Either way, UID-level compromise reaches it: the keyring raises the bar, it is not a TPM. * **The child process** — once `run` injects env vars, the child owns them (`/proc//environ`, core dumps, the child logging its own env). cuora's guarantee ends at `exec`. * **Deploy sinks** — `cuora deploy` pushes plaintext into the sink by design (CF Secrets Store is write-only); a sink compromise is outside cuora's boundary — it crosses exactly once, at push. * **Access patterns** — `private_refs` hides *which* secrets exist, not *when* they're touched: request timing and row counts are still observable to the store. Oblivious access is out of scope. * **Host compromise in general** — an attacker who can read your memory, ptrace the broker, or impersonate your UID already won; cuora raises the bar for *accidental* and *casual* leaks, not root. ## In one line cuora makes the *default* posture "values never appear in repos, prompts, args, logs, or agent context" — and records every time one still crosses. --- --- url: /cuora/explanation/boundaries.md --- # Boundaries cuora is one organism in a stack — it owns **secret materialization** and nothing else. If a feature needs another kind of memory or authority, it belongs to a neighbor. | neighbor | owns | how it meets cuora | | --- | --- | --- | | **myco** | work memory — tasks, issues, proposals, plans, ADRs, handoffs | this repo's `project/` ledger is myco's; cuora has no PM surface | | **sturn** | ephemeral coordination — live claims/ops/topics | not secrets: sturn state is transient, cuora state is sealed | | **kuda** | code memory — symbol/index knowledge | cuora refs name *secrets*, not code entities | | **peregrin** | orchestration — campaigns, multi-actor arbitration, policy | campaigns consume secrets via `cuora run`; audit aggregation + real policy are peregrin's (`CUORA_ACTOR` is self-claimed inside cuora); `cuora serve` — the ndjson IPC wire + `[serve.pdp]` exec consult (ADR-0003) — is its embedding point | | **bupha** | CI gate | asserts no plaintext secrets land in pipelines; `cuora check` in pipeline steps | | **mimus** | critic | secrets resolve env → cuora → legacy (keyring, `~/.config/mimus/env`) via `mimus-cuora` shelling `cuora`; vaults are canonical, writes land in them | | **sepia** | session lifecycle | session wrappers can capture cuora's stderr audit stream | ## What that means in practice * **A hosted vault service is in scope** — `cuora-vault-cf` is a store impl behind the same port, ciphertext-only, self-hosted. * **A daemon is out of scope** — persistent coordination, multi-machine views, arbitration between actors → peregrin. cuora stays a sync, embeddable binary: env vars are the interface, no SDK. * **Policy is out of scope** — `[guardrails]` are advisory accident prevention. Enforcement, identity, and unforgeable attribution are orchestration concerns. * **The audit wire stops at stderr** — emitting ndjson is cuora's; collecting, storing, and alerting on it is peregrin's (ADR-0001 deliberately removed the local `audit.jsonl`). --- --- url: /cuora/explanation/crypto-model.md --- # Crypto model One crypto choice everywhere, made once in ADR-0001: **age**. Every vault impl stores age ciphertext; the same identity unlocks the local file vault and the remote `cf` vault alike. ## The pieces * **Identity** — one X25519 keypair, held by an `cuora-identity` impl (ADR-0002): the **session keyring** (secret-service, macOS Keychain, Credential Manager) by default — fail closed — or the `~/.config/cuora/identity.txt` keyfile (mode `0600`) under `auto`/`file` modes or an explicit pin. An explicit file — `CUORA_IDENTITY_FILE` or `[vaults.*].identity` — pins the keyfile outright; that is the headless/CI path. `[identity] backend` (or `CUORA_IDENTITY_BACKEND`) selects the mode: `keyring` (default) fails closed — no reachable store or stored identity is an error, the keyfile is never read; `auto` is the fallback described above — convenient on headless/CI, but a *silent* downgrade if a desktop session loses its store, so it's opt-in; `file` skips the store probe entirely. `cuora init` bootstraps whichever backend applies and prints the public `age1…` recipient. On a reachable-but-empty Secret Service (fresh daemon, no collections yet) the keyring impl claims the `default` collection alias on first use — `dbus-secret-service-keyring-store`'s create path can't bootstrap one itself (I-006). * **Recipients** — `[vaults.*].recipients` encrypts to extra public keys beyond the identity's own — a team recipient list without sharing the private key. * **Ciphertext at rest** — the local vault is a single age-encrypted file (`vault.age`); the `cf` vault stores per-secret age ciphertext in D1. Neither store — local disk, remote worker — holds plaintext or key material. * **Seal/open at the edge** — values are plaintext only inside the cuora process, and only for as long as the op needs them. `Secret` is a `Zeroizing` newtype: memory is scrubbed on drop, and nothing but `.expose()` reads it. * **HMAC alongside age** — the `cf` wire uses HMAC-SHA256 twice (P-003, P-004), keyed but unkeyed-payload, complementary to the age envelope: request *signing* so tokens never travel, and `ref_key` *derivation* so storage keys are unlinkable. Neither is a second encryption primitive — it's authentication and deterministic naming. ## Why age (and not KMS / libsodium / SSH keys) * **One identity, every backend** — a remote vault that stores client-side ciphertext needs an offline asymmetric primitive; age gives recipient-style encryption with no service to trust. * **Dependency posture** — the `age` crate (rage's Rust impl) is pulled with `default-features = false`: binary ciphertext, sync API, no async/armor/plugin machinery — the binary stays light per the workspace rules. * **Interop for free** — the vault file decrypts with stock `rage`/`age` tooling, so a human is never locked into the CLI for disaster recovery. ## The cf wire (`cuora-vault-cf/2`) * **Request signing** — `Authorization: CuoraHmac <token_id>, <ts>, <sig>` with `sig = hex HMAC-SHA256(token, method+path+ts+ sha256hex(body))`. The token is the HMAC key and never crosses the wire; a ±60s timestamp window bounds replay. Worker-side, `CUORA_VAULT_TOKENS` maps `token_id → {env → {read, write}}` scopes — per-env credentials instead of one static bearer (which stays accepted during migration). * **Private refs** — with `[vaults.*] private_refs = true`, the storage key is `ref_key = hex HMAC-SHA256(k, env + "/" + ref)` where `k = HMAC-SHA256(identity, "cuora-cf/private-refs/v1")`. Derivation is deterministic (lookup is a plain keyed read) and identity-bound (rotating the identity re-keys everything, via `cuora migrate --private-refs`). The real ref travels inside a `name` blob — age ciphertext — so `list` maps keys back while the store sees only hex. * **Constant-time compares** — every token/signature comparison on the worker is a byte-wise XOR-fold over the longer input with length folded in; no early exit, no prefix oracle. ## What is deliberately not here * **No KMS port yet** — managed-key backends (AWS KMS/Secrets Manager) were explored in the `cuora-vault-aws` spike to prove the `Vault` port generalizes to non-age stores; a KMS-backed *crypto* port is a later ADR, not v1. * **No passphrase-wrapped identity file** — when the keyring is unavailable the keyfile is still raw age key material at `0600`, like an unencrypted SSH key. A passphrase-wrapped arm of the identity port is a later impl, not a new port. * **No signing / integrity metadata** beyond what age's AEAD gives — tampered ciphertext fails decryption loudly, which is the correct failure. --- --- url: /cuora/explanation/worktree-model.md --- # The worktree model Why `project/` and `docs/` live as git worktrees of orphan branches inside this repo — and what that buys. ## The shape ```text cuora/ ├── crates/ … ← code branch (main) ├── project/ ← worktree of the orphan `project` branch │ ├── TODO.md BACKLOG.md index.md meta.toml │ └── issues/ proposals/ plans/ adrs/ handoffs/ ├── docs/ ← worktree of the orphan `docs` branch │ └── this site — VitePress sources └── .gitignore ← ignores project/ and docs/ ``` Both worktrees share the repo's `.git` object store but have histories disjoint from `main` — orphan roots, no code ancestors. To tools they're ordinary directories; to `git worktree list` they're checkouts of their own branches. ## Why orphan branches Non-code state moves at its own cadence. On orphan branches, ledger and docs commits never touch the code history: no `chore: handoff T-009` noise in `main`'s log, no PM or docs-site diffs in code PRs. Both dirs are gitignored on `main` — the worktrees physically can't leak into a code commit. ## Why worktrees (not just branches) The content has to be *visible* — agents read `project/`, humans edit `docs/` — without `git checkout` swapping the code tree. Worktrees keep all three branches checked out at once. ## `project/` — the PM ledger Owned by **myco**, not cuora: this repo dogfoods the stack's PM tool. The `project` branch history is the audit trail — every mutating `myco` verb is one atomic commit. `myco init` created the branch and worktree; agents go through `myco` verbs rather than hand-editing when a verb exists. ## `docs/` — this site The `docs` branch holds the VitePress + Diátaxis sources; generated pages (`reference/cli.md`, `reference/config.md`, the crate map in `architecture.md`, `reference/crate-api.md`) are rebuilt by `docs/gen-*.py` and `cuora gen-cli-docs` — never hand-edited. `cargo xtask site` builds the site; pushes deploy to GitHub Pages at `9aia.com/cuora` via `scripts/deploy-docs.sh`. See [architecture](architecture.md) for the crates the code side is made of. --- --- url: /cuora/getting-started.md --- # Getting started Install the `cuora` CLI, seal your first secret in the local age-encrypted vault, and run a command that uses it — without the value ever touching your terminal, a file, or an agent's context. ## Install The crates.io name `cuora` belongs to an unrelated project — install from the repo: ```sh cargo install --git https://github.com/9aia/cuora cuora ``` From a checkout of this repo: `cargo xtask install` builds and installs the release binary to `~/.cargo/bin/cuora`. For a lean binary without the TUI: `cargo install --git https://github.com/9aia/cuora cuora --no-default-features`. ## Bootstrap ```sh cuora init ``` `init` stores your age identity where it's safest available and verifies the vault at `~/.local/share/cuora/vault.age` opens. It's idempotent — run it any time. On a desktop session (GNOME Keyring, KeePassXC, KDE Wallet, macOS Keychain, Windows Credential Manager) the identity lands in the session keyring — it unlocks with login, no prompt. The default backend is `keyring`: **fail closed** — if no session store is reachable, `init` (and every other verb) errors rather than silently downgrading to a file. Headless boxes (servers, CI, containers, plain SSH) have no session bus, so there you opt out explicitly — `[identity] backend = "auto"` in `cuora.toml` (probe the keyring, fall back to the keyfile with a note), `"file"` (never probe), or the `CUORA_IDENTITY_BACKEND` env var. The fallback writes `~/.config/cuora/identity.txt` (mode `0600`) — raw age key material, protect it like a private SSH key. `cuora init --with-file` forces the keyfile; pinning `CUORA_IDENTITY_FILE` or `[vaults.*].identity` in config does the same — that's the deliberate headless/CI path. Already have an `identity.txt`? `cuora init` imports it into the keyring rather than orphaning it, and leaves the file as a backup you can delete when ready. (On a brand-new keyring with no collections yet, cuora claims the `default` collection alias automatically — no manual step.) ## Seal a secret Values never enter via CLI arguments — they would land in shell history and `/proc`. Pipe them instead: ```sh printf '%s' "$STRIPE_KEY" | cuora put cuora://billing/stripe ``` Interactive humans get a hidden `/dev/tty` prompt instead of stdin. The value is encrypted to your identity and stored under the `dev` env (the default context). ## Declare the surface `[secrets]` in `cuora.toml` bounds what `run`/`env` may inject — least privilege, no ambient vault dump: ```toml [secrets] STRIPE_KEY = "cuora://billing/stripe" ``` Config search: `./cuora.toml` → `~/.config/cuora/cuora.toml` (`--config`/`$CUORA_CONFIG` override). No file at all is fine — defaults are the local vault and the `dev` env. ## Run with secrets ```sh cuora run -- 'curl -H "Authorization: Bearer $STRIPE_KEY" \ https://api.stripe.com/v1/charges' ``` `run` resolves the declared refs, injects them as env vars into the child only, and exits with the child's code. One arg after `--` runs via `bash -c` (shell syntax, as above); several args exec `argv[0]` directly with no shell. The value lives only in that process — agents never see it. ## Check the setup ```sh cuora check ``` Validates the config, that the routed vaults decrypt, and that every `[secrets]` ref resolves — exit `1` on findings. ## Agent self-discovery ```sh cuora manifest # verbs, JSON shapes, env vars, audit contract (JSON) cuora schema # JSON Schema for cuora.toml cuora --help # every verb, self-documenting ``` `--json` is honored on every verb; errors are `{"error": "…"}` on stderr with a non-zero exit. The [cuora skill](https://github.com/9aia/cuora/tree/main/.agents/skills/cuora) teaches agents the verbs. ## Where next * [Your first secret](tutorials/first-secret.md) — a guided end-to-end in a throwaway HOME. * [Inject into a process](how-to/inject-into-a-process.md) — run modes, ad-hoc `-s` pairs, the `eval "$(cuora env)"` human path. * [The threat model](explanation/threat-model.md) — what cuora does and does not protect against. * [CLI reference](reference/cli.md) — the full verb surface. --- --- url: /cuora/public/api/static.files/SourceSerif4-LICENSE-a2cfd9d5.md --- Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL *** ## SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1. Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2. Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3. No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5. The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.