feat(settings): show which master key is in force on the About tab - #111
Merged
Conversation
The auto-generated master key logs its back-this-up warning once, at generation. An operator who deploys and returns six months later sees nothing — and a per-boot warning would be worse, firing on every start of the documented default until people learn to skip startup logs. The About tab is where an admin actually looks, so the fact lives there. One row through the existing core/system_info.py -> api/settings.py path: master_key_info() reports the resolved source and path, AboutOut carries it, and AboutPanel renders "Auto-generated at <path> - back this up; a Docker secret gives stronger at-rest separation" or, for a supplied secret, "Supplied as a secret file at <path> - keep your copy backed up". Accurate for both sources rather than autogen wording shown unconditionally. Constraints: - No key material, and no key version either: source and path only, so this relocates an existing fact instead of disclosing a new one. - Admin-only on an endpoint that is not. GET /settings/about is readable by every role, so the row is populated per request for admins and omitted otherwise -- the path is deployment layout. Gated on effective_role, so an admin's role-capped API token sees what its role sees. - Never the reason a page fails: master_key_info() reads the cached startup resolution, so it does no filesystem work and cannot generate a key from a request; an unresolvable key omits the row rather than erroring the response. Tested through the API, matching how the other About rows are covered: both sources, no key material and no extra fields in the payload, and omission for a viewer, for a role-capped admin token, and when the resolution raises. See docs/ARCHIVE.md § Deviations (2026-07-29) for why this channel over a log line.
The master-key row is the only conditional logic on this tab, and inverting it is not a cosmetic bug: it would tell a deployment that supplied a Docker secret -- the recommended posture -- that its key was auto-generated on the data volume, and the reverse. That is wrong security guidance shown to the operators who did the right thing, so each branch is pinned to text the other cannot produce. Covers: the auto-generated source renders the auto-generated wording with the at-rest separation advice and not the supplied-secret wording; the secret-file source renders the supplied-secret wording without the separation advice; both render their back-it-up guidance; and the row is absent entirely when the response omits the field (a non-admin caller, or no resolvable key). Verified against a deliberately inverted conditional before accepting it: all four source-dependent cases fail, and the omission case correctly still passes since it does not exercise the branch.
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.
Follow-up to #109, branched fresh from
devafter that PR's squash-merge.Why
#109 logs the master key's back-this-up warning once, at generation. An operator who deploys and returns six months later sees nothing. The alternative considered and rejected in that discussion was a per-boot warning that the generated key shares a volume with the database — it would fire on every start of the documented default for most deployments, which is how operators learn to skip startup logs, costing the generation-time warning that actually matters. It also wouldn't work: nobody reads startup logs at month six.
The About tab is where an admin actually looks, so the fact lives there. The existing one-line per-boot source record is unchanged.
What
One row through the path the panel already uses —
core/system_info.py→app/api/settings.py→AboutPanel.tsx. No new endpoint, no new setting, no schema change.master_key_info()sits alongsidehost_info()and reports the resolved source (auto_generated/secret_file) and path.AboutOut.master_keycarries it; the panel renders eitheror, for a supplied secret,
Accurate for both sources rather than autogen wording shown unconditionally. Both say to back the key up, because losing it is equally fatal either way; only the separation advice is specific to the generated case.
Constraints it's built to
{source, path}.GET /settings/aboutis readable by every role (the SPA needs the instance name and health), so the row is populated per request for admins and omitted otherwise — the path is deployment layout a viewer has no need for. Gated onAuthContext.effective_role, notuser.role, so an admin's role-capped API token sees exactly what its role sees.master_key_info()reads the cached startup resolution, so it does no filesystem work and cannot generate a key from a request; if no key resolves (a dev instance, where the lifespan warns instead of failing) the row is omitted rather than erroring the whole About response.Testing
Matches how the other About rows are covered — through the API, since
host_info()has no unit test of its own either: both sources, the no-key-material / no-extra-fields assertions, and omission for a viewer, for a role-capped admin token, and when the resolution raises.AboutPanelhas no existing frontend test; I did not add one, to stay level with the rows already there — happy to add an RTL test for the two text variants if you'd prefer.Backend: 634 passed, 5 skipped;
ruff+blackclean;.env.examplein sync. Frontend: ESLint + Prettier clean, 59 tests passed,tsc -b+vite buildgreen.README § The master key gains a short "checking which key is in force" note;
docs/ARCHIVE.md§ Deviations has the dated entry recording this as the durable channel chosen over a per-boot log line.