Key handling
Keys are stored as SHA-256 digests. The plaintext is shown once and is not recoverable.
An API key is stored as a SHA-256 digest alongside its prefix and last four characters — the row proves a key was issued, never what it was.
wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44
└──────┘ └──┘
prefix last4What is stored
| Column | Holds |
|---|---|
prefix | wr_test_ or wr_live_. |
hash | SHA-256 of the full key. |
last4 | The final four characters, for display. |
environment | sandbox or production. |
scopes | Reserved; not currently enforced. |
last_used_at | Updated on every successful resolve. |
revoked_at | Null while live. |
The plaintext is shown once, when the key is created, and is not recoverable afterwards. Someone who reads the whole table gets a list of digests, which is the point.
Prefixes decide the environment
wr_test_... sandbox
wr_live_... productionNothing else does. A wr_live_ key presented against a sandbox organisation
resolves to no principal at all, which is a 401 rather than a 403 — there is
nobody to refuse. That is the same answer a made-up key gets, so probing tells
you nothing.
Revocation is immediate
Resolution filters on revoked_at IS NULL on every request. There is no cache
and no session to expire: a revoked key stops working on the next call.
Keys are for agents, and carry no role
An API key can open and verify warrants. It cannot resolve an escalation,
release, refund or partially release — those return 403
user_session_required.
If a key could approve a payment, the passkey threshold above which a person must sign would be decorative, because anyone holding a key could route around it. This is the single most important thing to understand about the key model.
Sessions for people
A person's session is an httpOnly cookie, never localStorage. The console is a
backend-for-frontend: it holds the cookie on its own origin and forwards it
server-side. No token reaches client JavaScript, so an XSS bug on the console
cannot exfiltrate one.
Where to keep yours
Environment variables, read at process start. The SDK reads WARRANT_API_KEY
and never takes a key as a literal in a call signature, which keeps it out of
stack traces and out of anything that logs arguments.
Do not put a key in a NEXT_PUBLIC_ variable. That prefix inlines the value
into the browser bundle at build time, permanently and publicly. This repo
lint-enforces the rule for anything that looks like a secret.
Other secrets in the same family
| Secret | Guards |
|---|---|
WARRANT_WORKER_SECRET | /internal/*. Absent or wrong is 401. |
TELEGRAM_WEBHOOK_SECRET | The bot webhook. Absent or wrong is 401. |
| Webhook signing secret | Outbound HMAC. Shown once, not recoverable. |
Both header secrets are compared with timingSafeEqual, and absent and wrong
return the identical error code so a prober learns nothing from the difference.