How it works

A small, deliberately unclever broker sits in front of Vault. Each surface where a person meets a credential gets its own origin and the strictest content security policy it can tolerate.

            Recipient browsers                         Operator browser
        ┌──────────┴──────────┐                              │
   Intake portal          Reveal page                 Operator console
   (zero JavaScript)      (one self-hosted script)    (authenticated)
        └──────────┬──────────┴──────────────────────────────┘
                   ▼  same-origin only
           ┌───────────────┐
           │ Broker (Hono) │   authn/z · workflow · audit emitter
           └───────┬───────┘
        ┌──────────┼───────────────┐
        ▼          ▼               ▼
      Vault    PostgreSQL      Audit sinks
   KV + wrap   metadata only   (Postgres, JSONL, WORM, SIEM)

The broker

The broker is a TypeScript service built on Hono and Drizzle, with a thin typed Vault client. It is the only component that talks to Vault and Postgres. Nowhere in its code is there a function that reads inbound data with the broker's own privilege. When a service account reads a submission, the broker makes the read as the caller's token. So a fully compromised broker host can mint submission links and create bogus shares, but it still can't read inbound credentials: it never held that capability.

Three front-ends, three origins

The intake portal and the reveal page are where untrusted recipients meet credentials, so they get the least code and the tightest policy:

Serving them from separate origins means a flaw in the richer console can't reach into the pages that handle credentials.

Postgres holds metadata, never secrets

This is a hard rule, stated at the top of the schema and enforced at runtime. Postgres holds no secret values, no Vault tokens, no wrap tokens, and nothing that could decrypt a secret. The only columns close to a secret are argon2id hashes and opaque Vault accessors. An attacker who copies the whole database gets a list of who asked whom for what, and not one credential.

One-time reveal, and why a second look is an alarm

An outbound share is identified in its URL by a Vault wrap-token accessor. The accessor is a high-entropy handle that is useless without a Vault token to act on it. The broker counts views and deletes the secret from Vault when the last allowed view is spent. Any attempt after that returns the same generic page, and is recorded as a denied reveal flagged possible_interception. A leaked link announces itself.

Audit you can check

Every event is hash-chained to the previous one and signed with a key derived for each tenant. The broker holds the master key, and it is never stored in the database. The same signed events go to WORM object storage (S3 Object Lock in compliance mode) and to your SIEM, over a webhook or syslog. audit:verify walks the chain and fails on any edit, deletion, reordering or forgery.

Machines, pipelines and agents

A reader is a named identity bound to specific intakes. Readers authenticate to Vault by AppRole, by an existing token, or by exchanging a CI job's OIDC token. The last option means no secret has to be stored to fetch a secret.

Running it

It runs on a single Linux box with Docker Compose: Vault, Postgres behind PgBouncer, and Traefik with automatic TLS. Production deployments can use a three-node Vault Raft cluster with auto-unseal. Helm, Terraform (AWS, GCP, Hetzner) and Ansible modules are included.

The full adversary-by-adversary analysis is in the threat model.