Repository navigation
fix(web): let /apps Edit reveal a masked credential instead of offering "xxx" - #5243
Merged
Merged
Conversation
…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
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
springfall2008
commented
Sep 26, 2026
… 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>
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.
Problem
Since #5053, the
/appspage masks credentials withmask_secret_args()before rendering. That is correct for what the page shows. But each row'sdata-original-value/data-nested-originalis also built from the masked copy. So clicking Edit on a credential filled the input withxxx.You could not see or change the real key, only retype it from scratch. Saving it unchanged was already refused by the
SECRET_MASKguard inhtml_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()addsdata-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./debug_apps?masked=0download does not already expose behind the same access.revealSecretValue():editValue()/editNestedValue()call it for adata-secretrow before opening the input.xxx.The existing server-side refusal to save
xxxover a credential is unchanged.Tests
Added to
tests/test_web_apps_edit.py. They failed before the fix and pass now:data-secret. A non-credential sibling does not. Neither credential appears anywhere in the page HTML./apps_valuereturns the real value for nested and list-nested credentials, and for a plain value./apps_valuerefuses a missing path, an unknown path, and a whole group of settings./apps_valuefor adata-secretrow.Also checked:
./run_all --quickand pre-commit pass.node --check.fetch: Edit on a maskedoctopus_api_keyrow requested./apps_value?path=octopus_api_keyand 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
unmatched_args) are still rendered unmasked on/apps.!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