Skip to content

fix(web): let /apps Edit reveal a masked credential instead of offering "xxx" - #5243

Merged
springfall2008 merged 3 commits into
mainfrom
fix/apps-page-reveal-secret-on-edit
Sep 26, 2026
Merged

springfall2008 merged 3 commits into
mainfrom
fix/apps-page-reveal-secret-on-edit

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Problem

Since #5053, the /apps page masks credentials with mask_secret_args() before rendering. That is correct for what the page shows. But each row's data-original-value / data-nested-original is also built from the masked copy. So clicking Edit on a credential filled the input with xxx.

You could not see or change the real key, only retype it from scratch. Saving it unchanged was already refused by the SECRET_MASK guard in html_apps_post(), so no key was lost. The editor was just unusable for credentials.

Fix

The page itself stays masked. No credential is written into the HTML.

  • WebInterface.secret_row_attr() adds data-secret="1" to any row whose value was masked, top-level and nested (e.g. chat.providers.openrouter.api_key).
  • GET /apps_value?path=<dotted path> (WebInterface.html_apps_value) returns the real value for one setting.
    • It refuses a missing path or anything that is not a single value (a whole list or group of settings), so it cannot pull a block of credentials out at once.
    • It exposes nothing that the unmasked /debug_apps?masked=0 download does not already expose behind the same access.
  • JS revealSecretValue(): editValue() / editNestedValue() call it for a data-secret row before opening the input.
    • It replaces the row's original with the fetched value, so an unchanged Apply is a no-op rather than a write.
    • It also means the value type is worked out from the real value, not from xxx.

The existing server-side refusal to save xxx over a credential is unchanged.

Tests

Added to tests/test_web_apps_edit.py. They failed before the fix and pass now:

  • Credential rows render masked and carry data-secret. A non-credential sibling does not. Neither credential appears anywhere in the page HTML.
  • /apps_value returns the real value for nested and list-nested credentials, and for a plain value.
  • /apps_value refuses a missing path, an unknown path, and a whole group of settings.
  • The page script's Edit handlers fetch /apps_value for a data-secret row.

Also checked:

  • ./run_all --quick and pre-commit pass.
  • The generated page script passes node --check.
  • A node run with a stubbed page and fetch: Edit on a masked octopus_api_key row requested ./apps_value?path=octopus_api_key and filled the input with the real key. Apply with no change queued nothing.

Not yet tried in a browser against a running Predbat.

Known, not addressed here

  • Once revealed, the real value stays visible in that row after Cancel, until the page refreshes. The user has explicitly clicked Edit on it.
  • Rows for settings Predbat does not recognise (unmatched_args) are still rendered unmasked on /apps.
  • Editing a value that apps.yaml takes from secrets.yaml (!secret) writes the plain value into apps.yaml. This behaved the same before fix(security): redact secrets at log-write time, not just at serve time (#4770) #5053.

🤖 Generated with Claude Code

…ng "xxx"

Since #5053 the /apps page masks credentials through mask_secret_args() before
rendering, which is right for what the page shows but also made the masked
copy the row's data-original-value. Clicking Edit on a credential therefore
filled the input with "xxx": the real key could be neither seen nor amended,
only retyped, and saving it unchanged was (correctly) refused.

The page stays masked. Rows whose value was masked now carry data-secret="1",
and Edit on such a row fetches the one real value from a new GET
/apps_value?path= endpoint before opening the input, replacing the row's
original with it so an unchanged Apply is not seen as a change. The endpoint
returns a single scalar only - a container or unknown path is refused - and
exposes nothing the unmasked /debug_apps download does not already.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@springfall2008 springfall2008 added the BOT_REVIEW Trigger an autotriage label Sep 26, 2026
Comment thread apps/predbat/web_helper.py
Comment thread apps/predbat/web.py Outdated
Comment thread apps/predbat/web.py Outdated
Comment thread apps/predbat/web_helper.py
Comment thread apps/predbat/web_helper.py
Comment thread apps/predbat/web.py
Comment thread apps/predbat/tests/test_web_apps_edit.py
@springfall2008 springfall2008 added BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push and removed BOT_REVIEW Trigger an autotriage labels Sep 26, 2026
CI and others added 2 commits September 26, 2026 11:31
… them plain text

- editValue/editNestedValue set input.value as a property instead of interpolating it into
  the markup, so a quote in a revealed password can no longer truncate it on Apply
- getDisplayValueEntity escapes its output; every caller writes it to innerHTML
- a revealed row is marked data-revealed: it skips the entity dropdown and saves as type
  string, so a dotted key opens a text input and a digit-only key keeps its leading zeros
- /apps_value returns the stored value verbatim (no resolve_arg str.format, which raised on a
  brace-bearing credential) and refuses an IndexError from a negative index into an empty list
- secret_row_attr docstring now describes the masked-container case accurately
- tests: brace/quote/angle-bracket round trip, leading-zero save, negative index, JS guards

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@springfall2008 springfall2008 removed the BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push label Sep 26, 2026
@springfall2008
springfall2008 merged commit 6d394b7 into main Sep 26, 2026
2 checks passed
@springfall2008
springfall2008 deleted the fix/apps-page-reveal-secret-on-edit branch September 26, 2026 12:17
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