Skip to content

fix(admin): show resolved runtime values on /admin/settings (#7803)#7807

Merged
JohnMcLear merged 5 commits into
developfrom
7803-admin-settings-resolved-runtime
May 18, 2026
Merged

fix(admin): show resolved runtime values on /admin/settings (#7803)#7807
JohnMcLear merged 5 commits into
developfrom
7803-admin-settings-resolved-runtime

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

Closes #7803. /admin/settings no longer lies to operators when settings are env-substituted.

  • Server (adminsettings.ts socket load handler): emits a new resolved field alongside the existing raw-file results blob. The resolved field is the in-memory settings module — i.e. with ${VAR:default} substitutions already applied at boot by lookupEnvironmentVariables — run through a small redactor that replaces known sensitive paths (users.*.password, dbSettings.password, sso.clients[*].client_secret, sessionKey, …) with the sentinel string [REDACTED].
  • Existing results and saveSettings are unchanged, so the raw textarea and round-trip save preserve ${VAR:default} literals on disk. Container redeploys keep working untouched.
  • Admin SPA: the resolved payload is cached in the store; useResolvedAt(path) walks it via the existing jsonc-parser JSONPath. EnvPill (the widget that already detects env placeholders and lets you edit the default) now renders a → active value chip when a resolved value is available, or → •••••• with a tooltip when the server returned the [REDACTED] sentinel.
  • Old client + new server: harmless — old client ignores resolved.
  • New client + old server: resolved is null, useResolvedAt returns undefined, EnvPill omits the chip — degrades to today's behaviour.

Reproducer (now fixed)

docker run -e DB_TYPE=sqlite -e DB_FILENAME=/data/etherpad.db etherpad/etherpad

Before: /admin/settings shows the dbType pill's default as dirty (the template default).
After: same pill now also shows → active value: sqlite reflecting what Etherpad is actually running.

Test plan

  • Backend mocha: 10 unit tests for the redactor + 4 socket integration tests against a booted Etherpad asserting resolved.trustProxy === true and resolved.dbSettings.password === '[REDACTED]'. All 32 admin specs (existing + new) pass.
  • Admin frontend (tsx --test): 8 resolveByPath unit tests + 5 EnvPill render tests covering undefined / resolved / redacted / non-string / null states. 21 admin tests pass overall (note: admin/package.json test glob updated to include *.test.tsx since the only existing tests were *.test.ts).
  • pnpm gen:api && tsc --noEmit clean on admin/ and src/.
  • vite build of admin SPA succeeds.
  • CI green.
  • Manual smoke in a real container: docker run -e DB_TYPE=sqlite etherpad/etherpad, open /admin/settings, verify dbType pill shows → sqlite and any secret-shaped setting shows the redacted indicator.

Skipped: full Playwright e2e

I considered a Playwright test that boots Etherpad with DB_TYPE set and asserts the chip text in a real browser. Skipping for now because the existing layers already cover every link in the chain (redactor → socket payload → store → useResolvedAtEnvPill render output), and the Playwright admin-auth setup overhead is large for a 5-line widget change. Happy to add one if reviewers want it.

Allow-list for redaction

users.*.password, users.*.passwordHash, users.*.hash, dbSettings.password, dbSettings.user, sso.clients[*].client_secret, sso.clients[*].secret, sessionKey. Easy to extend in src/node/utils/AdminSettingsRedact.ts if other plugins land paths that need protection. dbSettings.filename is intentionally NOT redacted — operators need to verify their volume mounts.

🤖 Generated with Claude Code

JohnMcLear and others added 5 commits May 18, 2026 12:28
Side-channel resolved+redacted settings alongside raw file blob.
Form view dropdowns and env pill chips reflect actual runtime values
instead of falling back to template defaults. Save round-trip is
unchanged so ${VAR:default} literals stay intact on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure helper that walks the live settings module and replaces known
sensitive paths (users.*.password, dbSettings.password,
sso.clients[*].client_secret, sessionKey, …) with [REDACTED] sentinel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…7803)

Existing 'results' raw-file blob is unchanged so the textarea editor
and saveSettings round-trip continue to preserve \${VAR:default}
literals on disk. New 'resolved' field carries the in-memory settings
module run through the redactor — admin SPA can use it to show actual
runtime values next to env-var placeholders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Admin SPA now stores the resolved field from the /settings socket
payload and exposes useResolvedAt(path) to walk it. EnvPill renders a
"→ active value" chip when the path is resolved, or "→ ••••••" with a
redacted tooltip when the server returned the [REDACTED] sentinel.
Old-server fallback (undefined resolved) keeps current behaviour.

The admin test script glob now picks up .test.tsx alongside .test.ts
so the new EnvPill tests run under tsx --test.

Closes #7803.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Show resolved runtime values on /admin/settings with secret redaction

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Server emits resolved runtime settings alongside raw file blob
  - In-memory settings module with env vars substituted
  - Secrets redacted to [REDACTED] sentinel
• Admin SPA stores resolved values and displays them in EnvPill widget
  - Shows → active value chip for resolved env-substituted settings
  - Shows → •••••• indicator for redacted secrets
• Backwards compatible with old servers and clients
  - Old client ignores new resolved field
  - New client degrades gracefully when resolved is null
• Comprehensive test coverage for redactor, path walker, and UI components
Diagram
flowchart LR
  A["In-memory settings<br/>with env vars resolved"] -->|redactSettings| B["Redacted clone<br/>secrets → [REDACTED]"]
  B -->|socket emit| C["Admin SPA<br/>resolved field"]
  C -->|useResolvedAt| D["EnvPill widget"]
  D -->|renders| E["→ active value chip<br/>or → •••••• redacted"]
  F["Raw settings.json<br/>${VAR:default} intact"] -->|unchanged| G["Textarea + saveSettings<br/>preserve template"]
Loading

Grey Divider

File Changes

1. src/node/utils/AdminSettingsRedact.ts ✨ Enhancement +50/-0

Pure redactor for sensitive settings paths

src/node/utils/AdminSettingsRedact.ts


2. src/node/hooks/express/adminsettings.ts ✨ Enhancement +3/-1

Emit resolved field on socket load event

src/node/hooks/express/adminsettings.ts


3. src/tests/backend/specs/admin/adminSettingsRedact.ts 🧪 Tests +101/-0

Unit tests for redactor allow-list coverage

src/tests/backend/specs/admin/adminSettingsRedact.ts


View more (13)
4. src/tests/backend/specs/admin/adminSettingsResolved.ts 🧪 Tests +181/-0

Socket integration tests for resolved payload

src/tests/backend/specs/admin/adminSettingsResolved.ts


5. admin/src/utils/resolveByPath.ts ✨ Enhancement +17/-0

JSONPath walker for resolved settings lookup

admin/src/utils/resolveByPath.ts


6. admin/src/utils/__tests__/resolveByPath.test.ts 🧪 Tests +41/-0

Unit tests for path resolution edge cases

admin/src/utils/tests/resolveByPath.test.ts


7. admin/src/store/store.ts ✨ Enhancement +12/-0

Store resolved field and useResolvedAt selector

admin/src/store/store.ts


8. admin/src/components/settings/widgets/EnvPill.tsx ✨ Enhancement +42/-3

Render runtime value and redacted indicator chips

admin/src/components/settings/widgets/EnvPill.tsx


9. admin/src/components/settings/widgets/__tests__/EnvPill.test.tsx 🧪 Tests +86/-0

Component tests for resolved value rendering

admin/src/components/settings/widgets/tests/EnvPill.test.tsx


10. admin/src/components/settings/JsoncNode.tsx ✨ Enhancement +9/-1

Pass resolved value to EnvPill via hook

admin/src/components/settings/JsoncNode.tsx


11. admin/src/App.tsx ✨ Enhancement +4/-0

Store resolved field from socket payload

admin/src/App.tsx


12. admin/src/App.css ✨ Enhancement +26/-0

Styles for runtime value and redacted chips

admin/src/App.css


13. admin/package.json ⚙️ Configuration changes +1/-1

Update test glob to include .test.tsx files

admin/package.json


14. src/locales/en.json 📝 Documentation +3/-0

i18n keys for runtime value and redacted tooltips

src/locales/en.json


15. docs/superpowers/specs/2026-05-18-admin-settings-resolved-runtime-design.md 📝 Documentation +280/-0

Design specification for resolved runtime feature

docs/superpowers/specs/2026-05-18-admin-settings-resolved-runtime-design.md


16. docs/superpowers/plans/2026-05-18-admin-settings-resolved-runtime.md 📝 Documentation +1198/-0

Implementation plan with task-by-task breakdown

docs/superpowers/plans/2026-05-18-admin-settings-resolved-runtime.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

Looking for bugs?

Check back in a few minutes. An AI review agent is analyzing this pull request.

Grey Divider

Qodo Logo

@JohnMcLear JohnMcLear merged commit 4d998d6 into develop May 18, 2026
34 of 35 checks passed
@JohnMcLear JohnMcLear deleted the 7803-admin-settings-resolved-runtime branch May 18, 2026 12:28
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.

admin/settings shows raw settings.json template (with ${VAR:default} literals) instead of resolved runtime values

1 participant