← Keymaster Sign in

Soundstripe Engineering · System architecture · Reflects the repo on 2026-09-10

Keymaster

Soundstripe's internal config and secrets manager. One place where people define what every application needs in every environment, and one contract by which those applications receive it. This page explains what Keymaster is, how a value moves through it, and how a consumer host connects.

There is no secret. Only keys. Config and secrets are one model. Sensitivity is a per-key attribute.

Orientation

What Keymaster is

Keymaster is a thin, opinionated policy layer over a self-hosted secrets backend. It does not hand-roll cryptography or storage. It owns everything around the values: who can see and change them, how changes are audited, what a complete export looks like, and how consumers fetch it.

Encrypted backend

Infisical, self-hosted

Stores every value encrypted at rest with versions, environments, and imports. Keymaster provisions a project per app and an environment per managed environment.

Its native UI is break-glass only. Normal human and machine access never touches it directly.

Policy and control plane

Keymaster UI + API

A Rails 8 application. GitHub org/team sign-in, per-app and per-environment roles, the apps × environments × keys matrix, production step-up, append-only audit, expected-key manifests, machine credentials, and the export API.

Consumer client

The keymaster CLI

A standalone Go binary and the only supported consumer integration. It fetches a validated export and either serves values from an encrypted snapshot over a Unix socket or renders a 0600 env file.

Management plane: people change values

Engineers sign in with GitHub, edit cells in the matrix, and apply audited batches. Production writes need a stricter role plus fresh re-authentication.

Delivery plane: applications receive values

A consumer host holds an exact-scope machine credential. The CLI pulls a complete, provenance-checked export and exposes it locally. Keymaster never pushes.

Backend

Infisical v0.165.2 pinned in the staging bundle

Contract

keymaster.export.v2 v1 still served concurrently

CLI release

keymaster-cli-v2.1.0 private GitHub Release, Linux/amd64 + macOS/arm64

Environments

Staging live, manually deployed production is separately gated (P0/P1 open)

Big picture

The system at a glance

Three boundaries. People and custody on the left, the Keymaster service in the middle, and a consumer host on the right. Values cross a boundary only over the labeled arrows.

People and custody Keymaster service boundary staging today · production separately gated Consumer host Engineer browser · GitHub org/team member GitHub OAuth identity · org/team gate re-auth for production step-up 1Password custodian of Infisical's ENCRYPTION_KEY and every runtime secret-zero Break-glass operator human-only emergency access reconciled in Keymaster afterward Caddy TLS ingress Keymaster UI + API Rails 8 · matrix · RBAC · audit manifests · machine credentials export + reference API Infisical self-hosted · encrypted values versions · environments · imports Keymaster PostgreSQL metadata · policy · audit never a plaintext value Native Infisical UI break-glass only one superuser, kept in 1Password Infisical data services its own PostgreSQL database Redis · Infisical only Host custody exact-scope machine credential bootstrap command → decrypt key keymaster serve long-lived daemon · Go binary decrypt key held in memory only validates · refreshes every 5 min Snapshot age ciphertext on disk Unix socket 0660 · one UID peer-checked Consumer application web · workers · migrations adapter reads the socket at boot signs in HTTPS OAuth · step-up values via REST metadata + audit export v2 · HTTPS · bearer credential at boot admin session at daemon start encrypts serves lookups
Who owns what. Keymaster owns policy, audit, expected-key manifests, and the machine-credential lifecycle. Infisical owns encrypted values. A plaintext value exists in exactly three places: inside Infisical, inside the daemon's memory, and inside the age-encrypted snapshot on the consumer host. Dashed arrows are custody or emergency paths, not normal operation.
  • Nobody talks to Infisical directly. Humans go through the Keymaster UI. Machines go through the CLI. Direct Infisical API, CLI, UI, or reader-identity access is unsupported for consumers.
  • Keymaster's database is metadata-only. App, environment, and key definitions, role bindings, credential digests, manifests, and audit records. No values, ever.
  • Pull, not push. Consumers pull at deploy time and at a refresh interval. Keymaster has no push path, so a Keymaster outage stalls refreshes but does not take down warm nodes.

Management plane

How a person changes a value

Every write passes a chain of gates. Each gate fails closed, and the last step records an audit intent before the backend write and a result after it.

skipped when the target is non-production Sign in GitHub OAuth scopes read:org + user:email Org / team gate checked at the OAuth callback re-verified every 15 min Role check RoleBinding by GitHub ID global, app, or env scope Stage edits server-side draft on tmpfs expires 15 min after creation Production step-up same-user GitHub re-auth valid 10 min · reason required Apply as a batch audit intent → Infisical write → audit result recorded not a member → denied no binding → denied expired → restage stale → re-auth audit holds no value
Authentication and authorization are separate. GitHub org/team membership permits sign-in. Only an explicit RoleBinding permits access to an app. Session freshness is bounded: membership evidence and idle time expire at 15 minutes, absolute lifetime at 8 hours, after which a new OAuth exchange is required.

Who can do what

RoleRead valuesWrite non-productionWrite productionAdministerBinding scope
vieweryesnononoglobal, app, or environment
nonprod_editoryesyesnonoglobal, app, or environment
prod_editoryesyeswith step-upnoglobal, app, or environment
keymaster_adminyesyeswith step-upyesglobal only for Access, machine credentials, audit, and creating apps
  • Sensitive values are masked by default. Revealing one is a separate audited action, rate limited to 30 per minute per user. Reveal, copy, and edit all go through it.
  • Onboarding before first sign-in. A global admin can invite a verified work email with app- or environment-scoped roles. The invitation is claimed on first GitHub sign-in when the verified email matches, expires after 30 days, and never grants global scope.
  • Drafts are volatile on purpose. Staged edits live in a RAM-backed store on the Keymaster host, keyed by an opaque handle in the session. A restart or deploy discards them, and the UI asks you to restage.
  • Writes are versioned, never destructive. Infisical keeps value history. The matrix can reveal and restage a prior version through the same audited path.

Data ownership

What is stored where

Keymaster and Infisical hold mirrored structure but different content. Keymaster's rows describe and govern; Infisical's rows hold the encrypted values.

Keymaster PostgreSQL · metadata, policy, audit Infisical · encrypted values ManagedAppslug · name · infisical_project_id Projectone per app, matched by slug provisions ManagedEnvironmentslug · production flag · matrix order Environmentinfisical_slug · configurable per app provisions ManagedKeyname · sensitive (default true) · path — no value Secretencrypted value · version history name ↔ value ExpectedKeyManifestper app + env · key · source kind · source env Secret importinheritance link · preserves source provenance validates RoleBinding · MachineIdentityCredential · AuditRecord Machine identityper-env reader · provisioning is mode-dependent backend actor Also: PendingAccessInvitation · DeliveryModeSelection · RequestRateCounter
The credential table stores a digest, not a token. Raw bearer tokens are shown once at issuance with Cache-Control: no-store and never persisted. Audit records keep the historical target IDs even after an app, environment, or key is deleted, and the ordinary update and delete paths on them are blocked.

Inheritance

Non-production may inherit. Production never does.

Within one app, a non-production environment can inherit a key's value from production or from another non-production environment. Resolution happens at read time through Infisical imports, and an explicit override always wins.

non-production may read production's value may inherit may inherit dev non-production staging non-production prod production · explicit values only production never inherits · an export that would resolve a non-production source fails closed as wrong-environment-import
One-directional by design. Inheritance is per app; cross-app sharing is out of scope. Keymaster inspects import provenance on every export and every matrix read, so a wrong-environment value can never be materialized into a production write.
  • Explicit override wins. An inheriting environment that sets its own value stops inheriting until that override is removed.
  • Sensitive inheritance needs read rights on the source. Inheriting a sensitive production value downward widens who can see it, so the editor must be allowed to read the source environment.
  • Manifests track provenance. An inherited key appears in the consuming environment's expected-key manifest with source kind inherited and its source environment, so the export checks both completeness and origin.

Delivery plane

How an application receives values

The CLI asks for one app and one environment. The export path checks the credential, the manifest, the backend listing, and the provenance of every resolved value before it returns anything. The CLI then re-validates the payload before it becomes a local delivery surface.

keymaster CLI GET /api/v1/export Authorization: Bearer … Credential active · exact app + env delivery authority Manifest an active expected-key manifest must exist for app + env Infisical read list secrets with imports every expected key must resolve Provenance no non-production source inside a production export Payload values · expectedKeys fingerprint · keyCount auth-fail · 401 partial-export · 422 tool-unreachable · 503 missing key → partial-export wrong-environment-import malformed-output · 422 JSON ≤ 10 MiB over HTTPS CLI validates before use contract version · expected keys present fingerprint recomputed · key grammar ^[A-Z][A-Z0-9_]*$ one mode per app + environment fleet Encrypted serve · production posture age snapshot on disk · values over a Unix socket Plaintext render · fallback / reference 0600 env file · atomic replace · keys sorted Consumer application reads one local surface at boot · never Infisical
Every failure has one name. Backend behavior is normalized into a stable error taxonomy so a consumer adapter can decide what to do without parsing Infisical responses. A successful export records an audit event with the key count, fingerprint digest, and a count of ignored backend-only keys, never a key name or value.

The export payload

{
  "contractVersion": "keymaster.export.v2",
  "app": "soundstripe-api",
  "environment": "prod",
  "generatedAt": "2026-09-10T14:00:00Z",
  "keyCount": 123,
  "fingerprint": {
    "algorithm": "sha256",
    "valueSetSha256": "<hex>",
    "canary": "keymaster-canary:soundstripe-api:prod:v1"
  },
  "expectedKeys": [
    { "key": "SECRET_KEY_BASE", "sourceKind": "local",
      "sourceEnvironment": "prod", "sourcePath": "/" }
  ],
  "values": [
    { "key": "SECRET_KEY_BASE", "value": "…", "sensitive": true,
      "source": { "kind": "local", "environment": "prod",
                  "path": "/", "version": 1 } }
  ]
}

The fingerprint is a SHA-256 over the sorted resolved entries, including values. It is computed in memory and only the digest is ever stored or logged. Key names reserve the KEYMASTER_ prefix for delivery metadata.

Normalized errors and CLI exit codes

ExitOutcomeConsumer does
10auth-failWarm node may reuse bounded last-known-good if configured. New node fails.
11tool-unreachableSame bounded last-known-good policy. New node fails.
12unknown-keyFail loud.
13unknown-environmentFail loud.
14wrong-environment-importFail loud. Keep the existing valid surface untouched.
15partial-exportFail loud. Keep the existing valid surface untouched.
16malformed-outputFail loud.
17stale-exportLocal surface is valid but behind. Refresh, then recheck.
18legacy-snapshotRe-bootstrap or refresh. Never hand-edit a header.
19sensitive-reference-refusedUse a permitted key. Do not retry with delivery authority.

Encrypted serve

On the consumer host

The production-oriented delivery mode. A long-lived daemon owns the decrypt key in memory, keeps the value set encrypted on disk, and answers local lookups over a Unix socket that the kernel restricts to one UID. Application processes never receive the machine credential or the decrypt material.

Consumer host · one app + environment fleet unit Keymaster export API over HTTPS 1Password / host bootstrap source of the decrypt material keymaster serve long-lived · supervised independently of the app locked memory · no core dumps decrypt material from the bootstrap command plaintext payload + fingerprint Unix socket 0660 · one kernel-checked UID one JSON request → one response Encrypted snapshot age ciphertext + metadata header daemon-owned · replaced atomically offline rotation with old + new key Application processes · same UID web workers migrations adapters: Ruby · Go · Python · Node lookups at boot · any failure fails the boot full export every 5 min + up to 30 s jitter · and on refresh bootstrap command runs once at daemon start · output never touches disk serves encrypt + rename decrypt at restart lookups by key
Encryption at rest buys disk and backup posture, not runtime secrecy. Plaintext exists in daemon memory either way. Deploy hooks and operators refresh through the same socket with keymaster refresh --socket; a failed refresh keeps the last valid payload. Startup refuses to proceed unless core dumps are disabled and memory locking succeeds, unless the development-only override is used, which never counts as fleet evidence.

Daemon lifecycle

SituationWhat the daemon doesIf Keymaster is unreachable
New node / code bootRuns the bootstrap command, fetches the current export, validates it, writes the encrypted snapshot, opens the socket.Fails closed. The node must not complete boot.
Power cycle / restartMemory is gone. Re-runs the bootstrap command, then refreshes from Keymaster.Fails closed by default, because the decrypt material may have rotated while the daemon was down.
Warm deployDaemon keeps running. The deploy hook asks it to refresh through the socket.Bounded last-known-good may be used only for auth-fail and tool-unreachable, and only when configured.
Runtime lookupAnswers from memory. Remote Keymaster is never in the per-lookup path.Unaffected.
Automatic refreshEvery 5 minutes plus up to 30 seconds of jitter (minimum 1 minute, or explicitly disabled). Same validation and lock as a manual refresh.Keeps the last valid disk and memory payload, records a value-free result, retries next cycle.

Socket protocol keymaster.serve.v1

RequestResponse
{"key":"NAME"}The value, sensitive or not, for the authorized peer UID.
{}Value-free ping: {"ok":true}.
{"action":"refresh"}Fetch, validate, encrypt, replace snapshot and served payload.
{"action":"status"}Readiness and current / stale-export against the remote fingerprint.

One connection carries one request and one response, then the server closes. Requests are capped at 64 KiB, connections at 64, and work at 30 seconds. No TCP listener exists.

Custody modes

  • daemon_only, preferred. The host custodian supplies the machine credential and bootstrap command only to the daemon. Application processes see neither.
  • managed_runtime, accepted exception. A platform such as Cloud66 that cannot isolate the daemon's channel may expose the exact-scope credential through managed configuration. Bootstrap material still never reaches the application, and the credential can never widen into direct Infisical access.
  • Plaintext render remains supported as the reference and fallback path: a 0600 env file written atomically, sourced by non-interactive bash, with metadata keys for contract version, app, environment, generation time, and fingerprint.

Machine identity

Machine credentials

A machine credential is a consumer host's secret-zero for Keymaster. It is an exact grant to one app and one environment; app-wide and global machine credentials do not exist.

PropertyRule
Issued byA global keymaster_admin only, from Admin → Access → Machine credentials. Every issue, use, and revoke is audited.
ScopeExactly one app and one environment. A valid token for a different app or environment normalizes to auth-fail.
Authoritydelivery may call export and health. reference may call only the reference endpoint, which returns non-sensitive local values and refuses sensitive keys before any backend read. Neither can use the other's endpoints.
Token handlingGenerated in memory, shown once in the issuance response with no-store and no-referrer, never in a redirect, flash, session, log, or audit record. Only the digest is stored. Lost tokens are replaced, not recovered.
LifetimeNon-production defaults to 30 days and refuses anything past 90. Production issuance is disabled until separately authorized.
RotationIssue the replacement, install it through the host custody path, verify exact-scope access, then revoke the old one. Revocation preserves the record; running processes keep working until their next fetch.
Rate limits60 exports per minute per credential and 300 per minute per client IP, counted before backend work. Limits return 429; a counter-store failure returns 503 without touching the backend.

Infisical reader identities are still in flux. The design calls for one least-privilege Infisical identity per app and environment. The pinned Infisical release rejected the additional-privilege API on staging, so staging runs in keymaster_api mode: Keymaster's own exact-scope credential is the consumer boundary, and no direct backend reader is created. A production-compatible scoped-reader design is an open gate.

Runtime topology

How it is deployed today

Staging runs from the source-managed bundle in deploy/staging/: Docker Compose under systemd on one host, with managed databases outside it. Production will use the same shape through a separately approved manual procedure until Control Room takes over.

Staging host · Docker Compose under systemd Caddy · the only public listener TLS for the Keymaster and Infisical hostnames Infisical v0.165.2 encrypted values native UI: break-glass only keymaster-web Rails + Thruster tmpfs drafts · Solid stack, no Redis runtime-secrets · Docker tmpfs per-service env files, held only while the stack runs 1password/op bootstrap containers ×2 fetch runtime vault items, write env files, stay attached to hold the mount Engineers browser · GitHub session Consumer hosts keymaster CLI · bearer credential 1Password runtime vault · service account root-owned token file = host secret-zero GitHub OAuth · org / team API Managed data · DigitalOcean PG: keymaster_staging + _cache · _queue · _cable PG: keymaster_inf_staging Redis: Infisical only 7-day point-in-time recovery HTTPS REST token exchange · membership Infisical DB + Redis writes env at start
The host keeps no persistent secret except one. A root-owned 1Password service-account token. Everything else (Infisical's ENCRYPTION_KEY and AUTH_SECRET, database URIs, the GitHub client secret, Rails SECRET_KEY_BASE, the Infisical integration token) is fetched at start into a tmpfs volume and disappears when the stack stops. Keymaster and Infisical share one PostgreSQL cluster but never a database; Redis belongs to Infisical alone.
  • Local development mirrors the shape. deploy/local/ runs Postgres 18 on host port 55432, Redis 7 for Infisical only, and Infisical, with per-worktree Rails and CLI processes on top.
  • Publishing is a pinned SSH path, not the public hostname. Releases are pushed from a trusted operator checkout to the host's direct administrative SSH endpoint with a pinned host key. Caddy is never a deployment path.
  • Recovery is rebuild plus restore. No host backups are taken. The application is rebuilt from source and the database restored, with the historical session scrub before it serves traffic.

Reference

Surfaces

The HTTP routes and CLI commands an engineer is most likely to meet. Machine routes require HTTPS; only explicitly named loopback development URLs may use HTTP, and redirects are refused.

HTTP

RouteWhoPurpose
GET /auth/githubhumanStart GitHub OAuth. Callback at /auth/github/callback.
GET /apps/:slughumanThe app's environments × keys matrix.
POST /apps/:slug/changeshumanStage edits. /changes/apply applies the audited batch; DELETE discards.
POST …/keys/:name/revealhumanAudited reveal of a masked value.
POST /production_step_uphumanRe-authenticate for production writes, with a reason.
GET /admin, GET /auditglobal adminInvite, Grant, Machine credentials; audit and activity reports (30 days default, 90 max).
GET /api/v1/exportdeliveryValidated export for app + environment. X-Keymaster-Contract: keymaster.export.v2 selects v2.
GET /api/v1/referencereferenceOne non-sensitive key's value; sensitive keys are refused.
GET /api/v1/healthdeliveryReachability and token check for the CLI.
GET /upanyoneRails health check.

CLI keymaster v2.1.0

CommandPurpose
exportFetch, validate, and write a 0600 env or JSON artifact.
getRead one non-sensitive key via the reference endpoint.
fingerprint, canaryValue-free metadata from a validated export.
statusCompare a rendered file or a daemon socket with the current export. Stale exits 17.
healthCheck the health endpoint. Does not prove convergence.
serveRun the encrypted-serve daemon.
refreshLive refresh through the socket, or offline snapshot refresh.
serve-getOperator lookup through the socket. Prints a value; never where output is logged.
rotateRe-encrypt a snapshot with old + new material while the daemon is stopped.
mode-checkVerify one delivery mode is selected for the fleet unit.
versionPrint the installed version.

Common flags default from KEYMASTER_BASE_URL, KEYMASTER_TOKEN, KEYMASTER_APP, and KEYMASTER_ENV. Passing a token on the command line requires the explicit --unsafe-token flag.

Orientation

Repo map and where to read next

web/The Keymaster UI and API. Rails 8, Ruby 3.4.8, PostgreSQL, Hotwire, RSpec. Services under app/services hold the domain: Authorization, AuditEvent, Api::Exporter, Infisical::*, Matrix::*, MachineCredentials::*. cli/The keymaster CLI. Go module github.com/SoundstripeEngineering/keymaster/cli. Export, render, encrypted snapshot, daemon, socket protocol, peer credentials. fixtures/consumer/A dependency-free Go consumer harness. Consumes only CLI-generated artifacts and exposes value-free diagnostics. Used for plaintext and encrypted-serve rehearsals. deploy/local/Compose stack for local development: Postgres 18, Redis 7, Infisical. deploy/staging/The manual staging bundle: Compose, Caddyfile, systemd units, 1Password bootstrap scripts, publish script, and tests. docs/specs/The interface contract and consumer requirements. The contract is normative; when this page and the contract differ, the contract wins. docs/consumer/The consumer package: onboarding, delivery modes, credentials and operations, CLI distribution, Rails on Cloud66, socket adapters. docs/system/Current repo shape, local workflow, configuration and entrypoints, access and operations policy, production deployment runbook. docs/plans/Active and completed plans. Work starts from WORKFLOW.md at the repo root.

Read next