Skip to content

feat(server): resolve the at-rest master key at boot (RIG-3655 T4) - #1146

Merged
trunk-io[bot] merged 3 commits into
compass-server/rig-3655-d8-admin-writesfrom
compass-server/rig-3655-t4-master-key
Sep 12, 2026
Merged

trunk-io[bot] merged 3 commits into
compass-server/rig-3655-d8-admin-writesfrom
compass-server/rig-3655-t4-master-key

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 7 PRs:

  1. main
  2. docs(server): scope user secrets per tenant, user, and agent (RIG-3655) #1110
  3. feat(envelope): AES-256-GCM seam for user-secret values (RIG-3655 T1) #1111
  4. feat(store): scope and encrypt user secrets at rest (RIG-3655 T2) #1118
  5. feat(secrets): add the DB-backed StoreResolver (RIG-3655 T3) #1138
  6. docs(server): require admin for tenant-scoped secret writes (RIG-3655) #1141
  7. "feat(server): resolve the at-rest master key at boot (RIG-3655 T4)" (this PR)
  8. test(server): prove the armed forge-secret boot path with the real resolver (RIG-3656) #1155

Adds a COMPASS_ reserved server-secret prefix and renames the at-rest
master key GATEWAY_CREDENTIALS_MASTER_KEY -> COMPASS_MASTER_KEY, since
one key now covers both the user-secret and gateway_credentials stores.
The rename is what the new prefix is for: the name carries neither
existing prefix, so without it the row is undeclarable.

resolveMasterKey resolves the key, decodes 64 hex chars to 32 bytes, and
reconciles it against the server_key_state tripwire -- first boot writes
a salted fingerprint, a later boot constant-time compares it and fails
closed, so a swapped key cannot mis-decrypt existing rows. It never
generates a key: operator-seeded custody, so an absent key is a startup
failure naming the provisioning runbook.

Errors never echo the value or any part of it, verified by a test that
sweeps every failure arm for any 8-char substring of the secret.

Also documents all seven server secrets in docs/self-host.md, which
named none of them before -- the master key's runbook is incoherent
without the six forge secrets beside it.

resolveMasterKey's boot caller lands with the rest of T4, which is
blocked on the session-binding work; the function, its store accessor,
and its tests ship complete.

Refs RIG-3655

Co-authored-by: Matt Wilkinson matt@rigel.build

Adds a COMPASS_ reserved server-secret prefix and renames the at-rest
master key GATEWAY_CREDENTIALS_MASTER_KEY -> COMPASS_MASTER_KEY, since
one key now covers both the user-secret and gateway_credentials stores.
The rename is what the new prefix is for: the name carries neither
existing prefix, so without it the row is undeclarable.

resolveMasterKey resolves the key, decodes 64 hex chars to 32 bytes, and
reconciles it against the server_key_state tripwire -- first boot writes
a salted fingerprint, a later boot constant-time compares it and fails
closed, so a swapped key cannot mis-decrypt existing rows. It never
generates a key: operator-seeded custody, so an absent key is a startup
failure naming the provisioning runbook.

Errors never echo the value or any part of it, verified by a test that
sweeps every failure arm for any 8-char substring of the secret.

Also documents all seven server secrets in docs/self-host.md, which
named none of them before -- the master key's runbook is incoherent
without the six forge secrets beside it.

resolveMasterKey's boot caller lands with the rest of T4, which is
blocked on the session-binding work; the function, its store accessor,
and its tests ship complete.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Sep 12, 2026

Copy link
Copy Markdown

RIG-3655

@rigel-mintaka
rigel-mintaka added this pull request to stack #1112 September 12, 2026 13:30
@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-3655-t4-m.compass-eng-docs.pages.dev

Deployed from compass-server/rig-3655-t4-master-key at 1fd0b7c.

Changed pages:

rigel-mintaka and others added 2 commits September 12, 2026 10:22
…G-3655 T4)

The docs finding was a real operator trap I introduced: the forge table
listed the two Linear secrets' built-in default names without saying
those defaults only apply when compass looks a value UP. The declaration
gate reads the RAW config, so provisioning a value under the default
name and setting neither flag nor env leaves Linear off with no boot
error. Documented explicitly.

Also: the tripwire comment claimed DELETE being revoked stops the digest
being dropped to defeat the key-swap check, which overstates it --
UPDATE is granted, so the tripwire catches an operator booting the wrong
key, not an actor with write access. Softened at the query source and
regenerated. The migration's prefix comment now enumerates all three
reserved prefixes, the salt comment says digest width rather than block
size, and the nolint reason no longer cites a task number.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
platform reviewed the forge table against the code and found the same
trap the review agent did, with one more fact: when neither Linear name
resolves, buildLinearTokenSource returns no token source and logs
nothing, because its half-configured warning needs exactly one of the
two to resolve. So the failure is fully silent.

A caveat under the table was not enough -- a reader scanning the rows
may never reach it. Dropping the Default name column removes the
misleading value at its source, and the prose now names the silent path.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io
trunk-io Bot merged commit 5c00a05 into main Sep 12, 2026
15 checks passed
@trunk-io
trunk-io Bot deleted the compass-server/rig-3655-t4-master-key branch September 12, 2026 18:37
@trunk-io

trunk-io Bot commented Sep 12, 2026

Copy link
Copy Markdown

This pull request was merged into main as part of stacked PR 1155.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants