Published in openvibe-contracts v0.177.0 (docs/adr/ADR-056-project-secrets.md), rendered as is.

ADR-056: Project secrets — written once, used by name, never read back

Status: Accepted 2026-10-15 (plan T13/T17: the "project secret store" that secret_ref waits on). Amends ADR-014 (Network owns projects and credentials) with one more thing a project holds, and ADR-044 §7 (Actor webhooks name a project secret by secret_ref).

Context

Decision

  1. Network keeps project secrets, per project and environment (sandbox, production), each under a name (^[a-z][a-z0-9_-]{0,63}$). A value is 1 to 8192 bytes of UTF-8, at most 50 secrets per project environment.
  2. Write-only. A project admin or owner sets a value (PUT /api/v1/projects/:project/secrets/:env/:name); setting it again replaces it and adds one to its version. No API returns a value to a person, staff included. Members (viewer and up) and staff read the metadata only: name, environment, version, a fingerprint, the grants, who set it and when (network.project-secret@1). The fingerprint is a keyed hash, so it tells two values apart without revealing either.
  3. Encrypted at rest with AES-256-GCM under a key held only in Network's environment (NETWORK_SECRETS_KEY, 32 random bytes). Each row carries its key id, so the key can be rotated. Without the key the feature is off (503 secrets.unavailable), never plaintext.
  4. A name is not a capability. A service uses a secret only when the project granted that secret to that service: PUT …/:name/grants/:service { purposes, origins }, admin or owner. There are two purposes:
  1. One first-party capability, network.secret.use, on POST /internal/secrets/use (network.secret-use-request@1 → network.secret-use-result@1). The caller is the service in its token (svc:<id>). A missing secret, a missing grant, a purpose or origin the grant does not list, and an archived project all answer 404 secret.not_found, so a guess learns nothing.
  2. Audit. Every value set, deletion and grant change is a dev_audit row, and so is the first use of each secret per service, purpose and UTC day. A grant records when it was last used. Values and messages are never written anywhere but the encrypted column.
  3. A project archived is crypto-shredded: its secrets' ciphertexts are deleted when it is archived, and its metadata stays in its audit.

Consequences