Skip to content

0006. Store integration credentials in one write-only secret store

Context

Automations and integration modules (social posting, GitHub issues, chat bots) need access tokens for other systems. Without a shared place to keep them, each module would invent its own storage, its own encryption and its own admin screen, and each would be a new place for a token to leak: through an API response, a log line, or a stored run step. The credentials also belong to an organization, not to the deploy, so environment variables don't fit: they are operator-only and set at deploy time.

Options

OptionForAgainst
Environment variablesNo new codeDeploy-wide, not per organization; only operators can change them; invisible to flow authors
Each module stores its own tokensEach module controls its own shapeRepeated crypto and admin UI; every module is a new leak path to audit
One shared, encrypted, write-only store per organizationOne audited path; one admin screen; modules only read on the serverValues can't be shown to admins; one connection per fixed name; org-wide, not per person

Decision

Add @monark/secrets: a per-organization store of named values, encrypted with AES-256-GCM under its own key (SECRETS_ENCRYPTION_KEY, separate from the two-factor key). No API procedure ever returns a value. Server code reads a value by name, scoped to the organization it's running for; automation nodes do it through ctx.getSecret, and a node's config stores the secret's name, never its value.

The deciding reason is a single, small surface that can't hand a value to the browser, so every new integration inherits the same guarantees instead of re-earning them.

Consequences

  • New integration modules get credential storage, encryption and an admin screen for free, and read values only on the server.
  • Flows and stored run steps hold names, so inspecting a run never reveals a token.
  • Admins can replace a value but never see it, which is right for credentials and wrong for ordinary settings; those still have no in-app home.
  • Any installed node type can read any secret of its organization. That's acceptable because node types are deployed server code.
  • Secrets are per organization, not per person, and rotating the encryption key is not supported.
  • Outbound webhook signing keeps its own pluggable store, so there are two secret mechanisms to explain to operators.