feat(server): resolve the at-rest master key at boot (RIG-3655 T4) - #1146
Merged
trunk-io[bot] merged 3 commits intoSep 12, 2026
Conversation
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>
This was referenced Sep 12, 2026
rigel-mintaka
added this pull request to stack #1112
September 12, 2026 13:30
|
Compass engineering docs preview: https://compass-server-rig-3655-t4-m.compass-eng-docs.pages.dev Deployed from Changed pages: |
…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>
mattwilkinsonn
approved these changes
Sep 12, 2026
|
This pull request was merged into |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is part of a stack containing 7 PRs:
mainAdds 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