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:
- Intake portal:
script-src 'none'. It is a server-rendered form, validated with native HTML constraints, with no scripts at all. - Reveal page: one self-hosted script, used for the reveal click, the copy button and the expiry countdown, with
connect-src 'self'. The page calls the broker through a same-origin proxy, so the browser only ever talks to its own host. - Operator console: authenticated with single sign-on (OIDC) or a local admin account, with a read-only auditor role.
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.
- CLI and SDKs in TypeScript, Python and Go. The CLI can pin the broker's TLS certificate.
get-secretAction for CI. It fetches a bound credential and masks it in the job log.- MCP server for AI agents. An agent asks, a human approves, and the value is written to a file on disk. It never enters the model's context.
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.