# Temporary access

seekrit can mint **short-lived credentials** on demand — a temporary Postgres
login that exists for an hour, then disappears — without giving up the
zero-knowledge property that defines the rest of the product. Postgres is the
first provider; the machinery is general.

## The problem it solves

A long-lived database password in a `.env` file is a standing liability: it
leaks, it lingers, it is hard to rotate. The Vault-style answer is **dynamic
secrets** — mint a credential per session, scoped and expiring. The catch is
that a naïve implementation has the control plane generate and hand out the
password, so the service sees your plaintext. seekrit avoids that.

## The key trick: ship the verifier, not the password

PostgreSQL stores passwords as **SCRAM-SHA-256 verifiers**, and `CREATE ROLE …
PASSWORD '…'` stores the string verbatim when it is already in verifier form —
it does not re-hash it. So the flow inverts:

1. The machine that will connect generates a random password **locally**.
2. It computes the SCRAM verifier locally (PBKDF2 → HMAC → SHA-256).
3. It sends seekrit **only the verifier**, plus a role name and a TTL.
4. seekrit runs `CREATE ROLE … PASSWORD '<verifier>' VALID UNTIL …`.
5. The machine connects to Postgres directly with the password it never shared.

The verifier is **not sufficient to authenticate** — SCRAM login requires
`ClientKey`, and the verifier only stores `SHA256(ClientKey)` (preimage-
resistant) plus `ServerKey`. So a dump of `pg_authid`, the query log, or a
backup cannot log in. The plaintext password lives only on the consumer.

> **Note:** This is zero-knowledge at **both** layers: the control plane relays only the verifier, and Postgres-at-rest holds only a verifier that can't log in. The password is high-entropy and machine-generated, so the SCRAM iteration count (which exists to slow dictionary attacks on human passwords) is irrelevant.

## Provider tiers

Not every system supports verifier injection, so providers are classified by
how much zero-knowledge they can preserve for the credential they mint:

- **Tier 1 — verifier injection.** The target accepts a client-computed
  verifier; plaintext never leaves the consumer. **Postgres SCRAM**, SSH
  public keys, mTLS CSR signing, hashed API keys.
- **Tier 2 — client-encrypted delivery.** The target mints the secret itself,
  so the broker wraps it to the consumer's public key before returning it. The
  control plane relays ciphertext; the broker sees plaintext transiently. AWS
  STS, GCP short-lived tokens.
- **Tier 3 — opaque broker.** The target mints an opaque token the broker must
  hold; the best option is to never hand it out and proxy the requests instead.

## The broker

Each organization gets its own **broker** (a Cloudflare Durable Object,
one instance per org). It owns the lease lifecycle in its own isolated SQLite,
and uses **alarms** to expire leases — the sweeper that drops the role at its
deadline (managed Postgres has no in-database scheduler). It holds two secrets
carefully:

- its **own keypair**, generated once, private half never leaving the DO;
- each target's **admin credential**, stored only as ciphertext wrapped to that
  public key, decrypted transiently in memory to provision, never written back.

## The provisioning credential — the honest caveat

To run `CREATE ROLE`, something must authenticate to Postgres as a privileged
role. That admin credential is a second, narrower trust tier — the same trade
every dynamic-secrets engine makes. seekrit minimizes it, and lets you choose
where it lives via the **executor**:

- **in-DO executor** — the broker connects to Postgres and runs the SQL itself.
  Convenient; the admin credential is decrypted transiently inside the DO.
- **remote executor** — the broker dispatches a signed command to a small
  `seekrit-provisioner` running inside your own network, which holds the admin
  credential. seekrit orchestrates but never sees it — **pure zero-knowledge
  even for provisioning**.

The credential you hand out stays zero-knowledge either way; the executor is the
dial for how much the *provisioning* credential is exposed.

## Provider compatibility

The verifier-injection trick relies on **standard** PostgreSQL behavior: a
pre-hashed `SCRAM-SHA-256$…` password is stored verbatim. That holds on
**Render, AWS RDS, Cloud SQL, Azure, Crunchy Bridge, self-hosted**, and most
managed Postgres. The in-DO executor authenticates its own admin connection via
**SCRAM, MD5, or cleartext** (over TLS), so it works whether a provider uses the
modern SCRAM default or still challenges MD5 for login.

> **Warning:** **Neon is not supported for the zero-knowledge path.** Neon manages roles through its own control plane, which intercepts `CREATE ROLE` and requires a **plaintext** password (it rejects a pre-hashed verifier: *"Neon only supports being given plaintext passwords"*). That is incompatible with sending only a verifier. Use a standard Postgres, or a provider whose role creation isn't proxied by a control plane.

See the [CLI reference](/docs/reference/cli) (`seekrit pg`) and the
[API reference](/docs/reference/api) for the endpoints.
