Skip to content

feat(settings): show which master key is in force on the About tab - #111

Merged
tyler-rich merged 2 commits into
devfrom
claude/system-panel-master-key-source
Jul 29, 2026
Merged

feat(settings): show which master key is in force on the About tab#111
tyler-rich merged 2 commits into
devfrom
claude/system-panel-master-key-source

Conversation

@tyler-rich

@tyler-rich tyler-rich commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Follow-up to #109, branched fresh from dev after 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.pyapp/api/settings.pyAboutPanel.tsx. No new endpoint, no new setting, no schema change.

  • master_key_info() sits alongside host_info() and reports the resolved source (auto_generated / secret_file) and path.

  • AboutOut.master_key carries it; the panel renders either

    Auto-generated at /data/app_secret_key — back this up; a Docker secret gives stronger at-rest separation.

    or, for a supplied secret,

    Supplied as a secret file at /run/secrets/app_secret_key — keep your copy backed up.

    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

  • No key material, and nothing else new. Source and path only — deliberately not the key version, which is surfaced nowhere else and would be a new disclosure rather than a relocation of an existing fact. Asserted in tests: the response body does not contain the key file's contents, and the object's keys are exactly {source, path}.
  • Admin-only, on an endpoint that is not. GET /settings/about is 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 on AuthContext.effective_role, not user.role, so an admin's role-capped API token sees exactly 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; 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. AboutPanel has 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 + black clean; .env.example in sync. Frontend: ESLint + Prettier clean, 59 tests passed, tsc -b + vite build green.

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.

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.
@tyler-rich
tyler-rich merged commit d3de051 into dev Jul 29, 2026
4 checks passed
@tyler-rich
tyler-rich deleted the claude/system-panel-master-key-source branch July 31, 2026 04:59
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.

1 participant