[GH-ISSUE #1608] [Bug] Scenario DOM keeps a declared secret that the page escapes #546

Closed
opened 2026-10-15 18:26:02 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @CassieFox23 on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1608

TL;DR: capture-browser-scenario removes a declared secret from the DOM only when it appears in its raw spelling. If the page shows it with &amp; or %20, the secret stays in the dom and accessibility artifacts. Secrets with &, <, " or a space are common in passwords.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

On 472f31323, a declared secret survives in two places when the page writes it in another spelling. The made-up secrets below are not real.

  1. The dom artifact is page.content(), which escapes &, < and > in text and & and " in attributes. A secret SECRET-a&b-7f3a comes back as SECRET-a&amp;b-7f3a. The raw spelling is gone, but the escaped one stays.
  2. A link href that holds the percent-encoded secret stays in both dom and accessibility (the /url: line). A secret SECRET c8 7f3a comes back as SECRET%20c8%207f3a.

Page text in the accessibility artifact is redacted. So are URLs, headers and network bodies. The gap is the dom and accessibility artifacts, for escaped spellings.

Reproduction

  1. Save this as x.html and serve it from 127.0.0.1:

    <!doctype html><title>x</title>
    <p>seen SECRET-a&amp;b-7f3a in the page</p>
    <a href="/next?t=SECRET%20c8%207f3a">go</a>
    
  2. Save this as scenario.json, with your port and Chromium path:

    {
      "browser": { "mode": "launch", "executable_path": "/usr/bin/chromium-browser" },
      "start_url": { "url": "http://127.0.0.1:8791/x.html" },
      "actions": [{ "step_id": "settle", "action": "wait_for_timeout", "duration_ms": 200 }],
      "capture": { "at_end": ["dom", "accessibility"] },
      "secrets": [
        { "secret_id": "text_token", "environment_variable": "REA_SEC_TEXT" },
        { "secret_id": "link_token", "environment_variable": "REA_SEC_LINK" }
      ]
    }
    
  3. Run:

    REA_SEC_TEXT='SECRET-a&b-7f3a' REA_SEC_LINK='SECRET c8 7f3a' \
      rea capture-browser-scenario scenario.json --json
    
  4. Read normalized_result.steps[-1].artifacts.dom.value.text. It holds SECRET-a&amp;b-7f3a and SECRET%20c8%207f3a. The accessibility text holds /url: /next?t=SECRET%20c8%207f3a. Its paragraph shows [REDACTED:text_token].

Expected behavior

The contract doc says REA removes declared secrets by itself. The network section also names URI/form and JSON-escaped spellings. The DOM and accessibility text should drop escaped spellings too.

Impact and scope

Scenario evidence returned to the caller can include a declared secret. It needs a secret with a character that HTML or URLs rewrite, and a page that shows it in text, a field value or a link. Plain letters, digits and dashes are not affected. I tested a typed field value with ": the DOM value attribute held &quot; and kept the secret.

This is separate from #1591 and PR #1590. The same output appears on main, on the PR head ed500f3bd, and on main with the PR patch applied. I ran this exact repro on all three. Cookie, storage, header and body values were fine in my tests.

REA version

Source revision 472f31323

Runtime and platform

Node 24.18.0, on Nobara 44 (Linux 7.2.9). Chromium 154, run headless.

Target and artifact context

Local test page in headless Chromium, with made-up secrets.

Interface

CLI

Earliest failure stage

Capture or observation

Analysis or suspected cause

Source-derived, not tested. captureDom and captureAccessibility in src/browser/PlaywrightScenarioArtifacts.ts call BrowserScenarioSecrets.redact. That method looks only for the raw secret. The URL and byte methods also look for encoded spellings. A fix might add the HTML-escaped and URI spellings to the text path. I have not tried that change.

Not run

  • Any change to the code.
  • Other encodings, such as base64 or Unicode escapes written by the page.
  • Other browsers.
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • Node: 24.18.0
  • Main: 472f31323
  • Other: Chromium 154 (BSD-3-Clause), the one Fedora 44 ships
  • Build: 154.0.8037.57

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

Originally created by @CassieFox23 on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1608 **TL;DR:** `capture-browser-scenario` removes a declared secret from the DOM only when it appears in its raw spelling. If the page shows it with `&amp;` or `%20`, the secret stays in the `dom` and `accessibility` artifacts. Secrets with `&`, `<`, `"` or a space are common in passwords. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior On `472f31323`, a declared secret survives in two places when the page writes it in another spelling. The made-up secrets below are not real. 1. The `dom` artifact is `page.content()`, which escapes `&`, `<` and `>` in text and `&` and `"` in attributes. A secret `SECRET-a&b-7f3a` comes back as `SECRET-a&amp;b-7f3a`. The raw spelling is gone, but the escaped one stays. 2. A link `href` that holds the percent-encoded secret stays in both `dom` and `accessibility` (the `/url:` line). A secret `SECRET c8 7f3a` comes back as `SECRET%20c8%207f3a`. Page text in the accessibility artifact is redacted. So are URLs, headers and network bodies. The gap is the `dom` and `accessibility` artifacts, for escaped spellings. ### Reproduction 1. Save this as `x.html` and serve it from `127.0.0.1`: ```html <!doctype html><title>x</title> <p>seen SECRET-a&amp;b-7f3a in the page</p> <a href="/next?t=SECRET%20c8%207f3a">go</a> ``` 2. Save this as `scenario.json`, with your port and Chromium path: ```json { "browser": { "mode": "launch", "executable_path": "/usr/bin/chromium-browser" }, "start_url": { "url": "http://127.0.0.1:8791/x.html" }, "actions": [{ "step_id": "settle", "action": "wait_for_timeout", "duration_ms": 200 }], "capture": { "at_end": ["dom", "accessibility"] }, "secrets": [ { "secret_id": "text_token", "environment_variable": "REA_SEC_TEXT" }, { "secret_id": "link_token", "environment_variable": "REA_SEC_LINK" } ] } ``` 3. Run: ```sh REA_SEC_TEXT='SECRET-a&b-7f3a' REA_SEC_LINK='SECRET c8 7f3a' \ rea capture-browser-scenario scenario.json --json ``` 4. Read `normalized_result.steps[-1].artifacts.dom.value.text`. It holds `SECRET-a&amp;b-7f3a` and `SECRET%20c8%207f3a`. The accessibility text holds `/url: /next?t=SECRET%20c8%207f3a`. Its paragraph shows `[REDACTED:text_token]`. ### Expected behavior The contract doc says REA removes declared secrets by itself. The network section also names URI/form and JSON-escaped spellings. The DOM and accessibility text should drop escaped spellings too. ### Impact and scope Scenario evidence returned to the caller can include a declared secret. It needs a secret with a character that HTML or URLs rewrite, and a page that shows it in text, a field value or a link. Plain letters, digits and dashes are not affected. I tested a typed field value with `"`: the DOM `value` attribute held `&quot;` and kept the secret. This is separate from #1591 and PR #1590. The same output appears on main, on the PR head `ed500f3bd`, and on main with the PR patch applied. I ran this exact repro on all three. Cookie, storage, header and body values were fine in my tests. ### REA version Source revision `472f31323` ### Runtime and platform Node 24.18.0, on Nobara 44 (Linux 7.2.9). Chromium 154, run headless. ### Target and artifact context Local test page in headless Chromium, with made-up secrets. ### Interface CLI ### Earliest failure stage Capture or observation ### Analysis or suspected cause Source-derived, not tested. `captureDom` and `captureAccessibility` in `src/browser/PlaywrightScenarioArtifacts.ts` call `BrowserScenarioSecrets.redact`. That method looks only for the raw secret. The URL and byte methods also look for encoded spellings. A fix might add the HTML-escaped and URI spellings to the text path. I have not tried that change. ### Not run - Any change to the code. - Other encodings, such as base64 or Unicode escapes written by the page. - Other browsers. <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - Node: 24.18.0 - Main: `472f31323` - Other: [Chromium 154](https://github.com/chromium/chromium) ([BSD-3-Clause](https://github.com/chromium/chromium/blob/main/LICENSE)), the one Fedora 44 ships - Build: `154.0.8037.57` </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
bsakaguchi 2026-10-15 18:26:02 +00:00
Author
Owner

@CassieFox23 commented on GitHub (Oct 10, 2026):

TL;DR: In my tests, #1611 fixes both leaks from this issue. As its description says, #1612 leaves those to #1611 and fixes a different gap: accessibility names with quotes or odd spaces. Neither PR covers the other, so a full fix needs both.

Results

Each form ran through capture-browser-scenario on headless Chromium 154 (BSD-3-Clause). Each run had one made-up secret. The base was c1217e711 (#1577), with #1611 at a5872d44a and #1612 at 2d546c5c5. I searched the whole output, stderr and the temp files for the secret in many spellings. Each cell below names the artifacts that still held it (both means dom and accessibility).

Form on the page main c1217e711 #1611 a5872d44a #1612 2d546c5c5
This issue's repro: text with &, link with %20 both clean both
&, <, > or " in text, attribute, title or textarea (8 forms) dom clean dom
%20, %2F, + or lower-case hex in a link (5 forms) both clean both
One secret with all of those, in text, link and field both clean both
No-break space in text, or in a label with a zero-width space (2 forms) both accessibility dom
" and \ in a label (JSON escapes) both accessibility dom
: and ' in a label (YAML doubles the quote) accessibility accessibility clean
Three spaces in text (folded to one) accessibility accessibility clean
JSON \" in an inline script block dom dom dom
Near miss, other case, double escape, plain &amp; and %20 kept kept, but see Near misses kept

Rows 2 to 9 hold 20 forms. Each form was one run per tree. Row 1 is one run with the two issue secrets. The last row is one run with three secrets declared, set against a run that declared none.

  • Superset: neither PR is one. The #1611 PR changes the dom and accessibility calls. The #1612 PR changes only the accessibility call, as its description says.
  • Near misses: all survived on every tree, apart from one case. A page that shows the text SECRET-a&amp;b-7f3a has it redacted by #1611 in accessibility, since that is the declared secret with HTML escapes. Its dom keeps it.
  • Markers: #1611 keeps [REDACTED:id]. #1612 writes [REDACTED] in accessibility, so the id is gone there.
  • Conflict: a dry run of patch shows one real clash. Both PRs rewrite the same line in captureAccessibility, and the second patch fails on that hunk in either order. The #1612 description expects this and plans to combine the two there. The test file was skipped as already applied, since both PRs add the same import line, so I did not test its second hunk. The other files apply, with offsets or fuzz. I built no merged tree, so I do not know that a merge works.
Check Node 22.23.1 Node 24.18.0 Node 26.11.1
#1611 changed tests (2 files) 35 of 35 pass 35 of 35 pass 35 of 35 pass
#1612 changed test (1 file) 13 of 13 pass 13 of 13 pass 13 of 13 pass
#1611 tests on main not run 20 fail, 15 pass not run
#1612 test on main not run 10 fail, 3 pass not run
#1611 tests on #1612 not run 20 fail, 15 pass not run
#1612 test on #1611 not run 10 fail, 3 pass not run
npm run check:fast on #1611 not run pass not run
npm run check:fast on #1612 not run pass not run
#1612 browser script on #1612 not run pass not run
#1612 browser script on #1611 not run fail not run
#1612 browser script on main not run fail not run

Each PR's tests fail with the same counts on main and on the other PR, so they do check that PR's own fix. The browser script runs headless on Linux. It starts its own Chromium and a local page server. It fails on #1611 and main with "Accessibility retained a declared secret". The script that ships in #1611 passes.

Not run

  • Other artifacts in the scenario output, such as storage, network bodies and events. I asked for dom and accessibility only.
  • A merged tree of both PRs.
  • Connect mode, headed mode and the MCP path in my own runs.
  • Other encodings, such as base64, &#38;, Unicode escapes and repeats. The PR texts and the issue's own Not run list leave these out.
  • #1614, which was closed in favor of #1611.
  • npm run check, docs:check and the full test suite. GitHub shows two failing test jobs on #1612 and no checks on #1611. The log lines I read name EVM and firmware tests, not browser code. I did not run them, so this is second-hand.
  • Other systems and other Chromium versions.

Thanks, Aliciawque, for #1611, which fixes both leaks from this issue and has tests that fail on main. Thanks, Y0UW1SH, for #1612, which closes a gap that #1611 names, and for a browser script that runs headless on Linux.

All 20 forms, one row each

Each cell names the artifacts that held the secret in some spelling. "clean" means no spelling anywhere.

Case What the page held main #1611 #1612
c01 text with & dom clean dom
c02 link with %20 both clean both
c03 input value with " dom clean dom
c04 text with < and > dom clean dom
c05 link with %2F and %26 both clean both
c06 link with + and %26 both clean both
c07 link with %20 and &amp; both clean both
c08 link with lower-case %2f both clean both
c09 mixed secret in text, link and field both clean both
c10 title with & dom clean dom
c11 data attribute with " dom clean dom
c12 textarea with < and > dom clean dom
c13 text with a no-break space both accessibility dom
c14 JSON in a script block dom dom dom
c15 label with : and ' accessibility accessibility clean
c16 label with " and \ both accessibility dom
c17 label with no-break and zero-width space both accessibility dom
c18 text with three spaces accessibility accessibility clean
c19 label with & dom clean dom
c20 label with < and > dom clean dom
Raw output: the issue repro on each tree
main
  dom: seen SECRET-a&amp;b-7f3a in the page
  dom: href="/next?t=SECRET%20c8%207f3a"
  accessibility: seen [REDACTED:text_token] in the page
  accessibility: /url: /next?t=SECRET%20c8%207f3a
#1611
  dom: seen [REDACTED:text_token] in the page
  dom: href="/next?t=[REDACTED:link_token]"
  accessibility: seen [REDACTED:text_token] in the page
  accessibility: /url: /next?t=[REDACTED:link_token]
#1612
  dom: seen SECRET-a&amp;b-7f3a in the page
  dom: href="/next?t=SECRET%20c8%207f3a"
  accessibility: seen [REDACTED] in the page
  accessibility: /url: /next?t=SECRET%20c8%207f3a

The accessibility text for three label cases:

c16 main and #1611: button "SYNTH-16\"q\\b-7f3a"
c16 #1612: button "[REDACTED]"
c15 main and #1611: 'button "SYNTH-15: ''q'' 7f3a"'
c15 #1612: 'button "[REDACTED]"'
c18 main and #1611: paragraph: SYNTH-18 b-7f3a
c18 #1612: paragraph: [REDACTED]
Patch dry run

patch -p1 --dry-run of #1612 on a fresh copy of c1217e711 with #1611 applied. The reverse order gave one failed hunk in the same file.

checking file src/browser/BrowserScenarioSecrets.ts
Hunk #2 succeeded at 70 with fuzz 2 (offset 49 lines).
Hunk #3 succeeded at 196 with fuzz 1 (offset 100 lines).
checking file src/browser/PlaywrightScenarioArtifacts.test.ts
Reversed (or previously applied) patch detected!  Skipping patch.
2 out of 2 hunks ignored
checking file src/browser/PlaywrightScenarioArtifacts.ts
Hunk #1 FAILED at 86.
1 out of 1 hunk FAILED
Commands
# trees from the read-only mirror, then build on Node 24.18.0
git -C ~/code/rea-mirror/rea.git archive <sha> | tar -x -C /tmp/p1608/<name>
npm ci && npm run build

# one run per form, with the secret in an environment variable
env -i HOME=... PATH=<node bin>:/usr/bin:/bin TMPDIR=... REA_SEC_X='<made-up value>' \
  node scripts/rea.mjs capture-browser-scenario scenario.json --json

# tests, per Node version
node scripts/run-development-tests.mjs focused \
  src/browser/BrowserScenario.test.ts src/browser/PlaywrightScenarioArtifacts.test.ts

# check:fast, in a clone checked out at each head
git clone --no-checkout <mirror> && git checkout --detach <sha> && npm ci && npm run check:fast

# browser script
node scripts/verify-real-browser.mjs /usr/bin/chromium-browser

Every run used env -i, a fake home folder, no D-Bus, ulimit -c 0 and nice -n 10. A small Python server on 127.0.0.1 served the pages. Each scenario captured dom and accessibility at the end, after a 200 ms wait. To run #1612's script on #1611 and main, I copied its four script files into a copy of each tree.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 22.23.1, 24.18.0, 26.11.1 (browser runs on 24.18.0)
  • Head / base: a5872d44a (#1611) and 2d546c5c5 (#1612) / c1217e711 (#1577). Neither head moved during the run.
  • Other: Chromium 154 (BSD-3-Clause), the one Fedora 44 ships
  • Build: 154.0.8037.57

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6101125011 --> @CassieFox23 commented on GitHub (Oct 10, 2026): **TL;DR:** In my tests, #1611 fixes both leaks from this issue. As its description says, #1612 leaves those to #1611 and fixes a different gap: `accessibility` names with quotes or odd spaces. Neither PR covers the other, so a full fix needs both. ### Results Each form ran through `capture-browser-scenario` on headless Chromium 154 ([BSD-3-Clause](https://chromium.googlesource.com/chromium/src/+/main/LICENSE)). Each run had one made-up secret. The base was c1217e711 (#1577), with #1611 at a5872d44a and #1612 at 2d546c5c5. I searched the whole output, stderr and the temp files for the secret in many spellings. Each cell below names the artifacts that still held it (both means `dom` and `accessibility`). | Form on the page | main c1217e711 | #1611 a5872d44a | #1612 2d546c5c5 | |---|---|---|---| | This issue's repro: text with `&`, link with `%20` | both | clean | both | | `&`, `<`, `>` or `"` in text, attribute, title or textarea (8 forms) | `dom` | clean | `dom` | | `%20`, `%2F`, `+` or lower-case hex in a link (5 forms) | both | clean | both | | One secret with all of those, in text, link and field | both | clean | both | | No-break space in text, or in a label with a zero-width space (2 forms) | both | `accessibility` | `dom` | | `"` and `\` in a label (JSON escapes) | both | `accessibility` | `dom` | | `: ` and `'` in a label (YAML doubles the quote) | `accessibility` | `accessibility` | clean | | Three spaces in text (folded to one) | `accessibility` | `accessibility` | clean | | JSON `\"` in an inline script block | `dom` | `dom` | `dom` | | Near miss, other case, double escape, plain `&amp;` and `%20` | kept | kept, but see Near misses | kept | Rows 2 to 9 hold 20 forms. Each form was one run per tree. Row 1 is one run with the two issue secrets. The last row is one run with three secrets declared, set against a run that declared none. - **Superset:** neither PR is one. The #1611 PR changes the `dom` and `accessibility` calls. The #1612 PR changes only the `accessibility` call, as its description says. - **Near misses:** all survived on every tree, apart from one case. A page that shows the text `SECRET-a&amp;b-7f3a` has it redacted by #1611 in `accessibility`, since that is the declared secret with HTML escapes. Its `dom` keeps it. - **Markers:** #1611 keeps `[REDACTED:id]`. #1612 writes `[REDACTED]` in `accessibility`, so the id is gone there. - **Conflict:** a dry run of `patch` shows one real clash. Both PRs rewrite the same line in `captureAccessibility`, and the second patch fails on that hunk in either order. The #1612 description expects this and plans to combine the two there. The test file was skipped as already applied, since both PRs add the same import line, so I did not test its second hunk. The other files apply, with offsets or fuzz. I built no merged tree, so I do not know that a merge works. | Check | Node 22.23.1 | Node 24.18.0 | Node 26.11.1 | |---|---|---|---| | #1611 changed tests (2 files) | 35 of 35 pass | 35 of 35 pass | 35 of 35 pass | | #1612 changed test (1 file) | 13 of 13 pass | 13 of 13 pass | 13 of 13 pass | | #1611 tests on main | not run | 20 fail, 15 pass | not run | | #1612 test on main | not run | 10 fail, 3 pass | not run | | #1611 tests on #1612 | not run | 20 fail, 15 pass | not run | | #1612 test on #1611 | not run | 10 fail, 3 pass | not run | | `npm run check:fast` on #1611 | not run | pass | not run | | `npm run check:fast` on #1612 | not run | pass | not run | | #1612 browser script on #1612 | not run | pass | not run | | #1612 browser script on #1611 | not run | fail | not run | | #1612 browser script on main | not run | fail | not run | Each PR's tests fail with the same counts on main and on the other PR, so they do check that PR's own fix. The browser script runs headless on Linux. It starts its own Chromium and a local page server. It fails on #1611 and main with "Accessibility retained a declared secret". The script that ships in #1611 passes. ### Not run - Other artifacts in the scenario output, such as storage, network bodies and events. I asked for `dom` and `accessibility` only. - A merged tree of both PRs. - Connect mode, headed mode and the MCP path in my own runs. - Other encodings, such as `base64`, `&#38;`, Unicode escapes and repeats. The PR texts and the issue's own Not run list leave these out. - #1614, which was closed in favor of #1611. - `npm run check`, `docs:check` and the full test suite. GitHub shows two failing test jobs on #1612 and no checks on #1611. The log lines I read name EVM and firmware tests, not browser code. I did not run them, so this is second-hand. - Other systems and other Chromium versions. Thanks, Aliciawque, for #1611, which fixes both leaks from this issue and has tests that fail on main. Thanks, Y0UW1SH, for #1612, which closes a gap that #1611 names, and for a browser script that runs headless on Linux. <details> <summary>All 20 forms, one row each</summary> Each cell names the artifacts that held the secret in some spelling. "clean" means no spelling anywhere. | Case | What the page held | main | #1611 | #1612 | |---|---|---|---|---| | c01 | text with `&` | `dom` | clean | `dom` | | c02 | link with `%20` | both | clean | both | | c03 | input value with `"` | `dom` | clean | `dom` | | c04 | text with `<` and `>` | `dom` | clean | `dom` | | c05 | link with `%2F` and `%26` | both | clean | both | | c06 | link with `+` and `%26` | both | clean | both | | c07 | link with `%20` and `&amp;` | both | clean | both | | c08 | link with lower-case `%2f` | both | clean | both | | c09 | mixed secret in text, link and field | both | clean | both | | c10 | title with `&` | `dom` | clean | `dom` | | c11 | data attribute with `"` | `dom` | clean | `dom` | | c12 | textarea with `<` and `>` | `dom` | clean | `dom` | | c13 | text with a no-break space | both | `accessibility` | `dom` | | c14 | JSON in a script block | `dom` | `dom` | `dom` | | c15 | label with `: ` and `'` | `accessibility` | `accessibility` | clean | | c16 | label with `"` and `\` | both | `accessibility` | `dom` | | c17 | label with no-break and zero-width space | both | `accessibility` | `dom` | | c18 | text with three spaces | `accessibility` | `accessibility` | clean | | c19 | label with `&` | `dom` | clean | `dom` | | c20 | label with `<` and `>` | `dom` | clean | `dom` | </details> <details> <summary>Raw output: the issue repro on each tree</summary> ```text main dom: seen SECRET-a&amp;b-7f3a in the page dom: href="/next?t=SECRET%20c8%207f3a" accessibility: seen [REDACTED:text_token] in the page accessibility: /url: /next?t=SECRET%20c8%207f3a #1611 dom: seen [REDACTED:text_token] in the page dom: href="/next?t=[REDACTED:link_token]" accessibility: seen [REDACTED:text_token] in the page accessibility: /url: /next?t=[REDACTED:link_token] #1612 dom: seen SECRET-a&amp;b-7f3a in the page dom: href="/next?t=SECRET%20c8%207f3a" accessibility: seen [REDACTED] in the page accessibility: /url: /next?t=SECRET%20c8%207f3a ``` The `accessibility` text for three label cases: ```text c16 main and #1611: button "SYNTH-16\"q\\b-7f3a" c16 #1612: button "[REDACTED]" c15 main and #1611: 'button "SYNTH-15: ''q'' 7f3a"' c15 #1612: 'button "[REDACTED]"' c18 main and #1611: paragraph: SYNTH-18 b-7f3a c18 #1612: paragraph: [REDACTED] ``` </details> <details> <summary>Patch dry run</summary> `patch -p1 --dry-run` of #1612 on a fresh copy of c1217e711 with #1611 applied. The reverse order gave one failed hunk in the same file. ```text checking file src/browser/BrowserScenarioSecrets.ts Hunk #2 succeeded at 70 with fuzz 2 (offset 49 lines). Hunk #3 succeeded at 196 with fuzz 1 (offset 100 lines). checking file src/browser/PlaywrightScenarioArtifacts.test.ts Reversed (or previously applied) patch detected! Skipping patch. 2 out of 2 hunks ignored checking file src/browser/PlaywrightScenarioArtifacts.ts Hunk #1 FAILED at 86. 1 out of 1 hunk FAILED ``` </details> <details> <summary>Commands</summary> ```sh # trees from the read-only mirror, then build on Node 24.18.0 git -C ~/code/rea-mirror/rea.git archive <sha> | tar -x -C /tmp/p1608/<name> npm ci && npm run build # one run per form, with the secret in an environment variable env -i HOME=... PATH=<node bin>:/usr/bin:/bin TMPDIR=... REA_SEC_X='<made-up value>' \ node scripts/rea.mjs capture-browser-scenario scenario.json --json # tests, per Node version node scripts/run-development-tests.mjs focused \ src/browser/BrowserScenario.test.ts src/browser/PlaywrightScenarioArtifacts.test.ts # check:fast, in a clone checked out at each head git clone --no-checkout <mirror> && git checkout --detach <sha> && npm ci && npm run check:fast # browser script node scripts/verify-real-browser.mjs /usr/bin/chromium-browser ``` Every run used `env -i`, a fake home folder, no D-Bus, `ulimit -c 0` and `nice -n 10`. A small Python server on 127.0.0.1 served the pages. Each scenario captured `dom` and `accessibility` at the end, after a 200 ms wait. To run #1612's script on #1611 and main, I copied its four script files into a copy of each tree. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 22.23.1, 24.18.0, 26.11.1 (browser runs on 24.18.0) - Head / base: a5872d44a (#1611) and 2d546c5c5 (#1612) / c1217e711 (#1577). Neither head moved during the run. - Other: Chromium 154 ([BSD-3-Clause](https://chromium.googlesource.com/chromium/src/+/main/LICENSE)), the one Fedora 44 ships - Build: 154.0.8037.57 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@CassieFox23 commented on GitHub (Oct 10, 2026):

TL;DR: On main 4999e4be2 (#1651), all 20 forms still keep the declared secret in dom, accessibility or both. That matches my earlier run on c1217e711 (#1577). #1611 and #1612 are still open.

Results

I used capture-browser-scenario on headless Chromium 154 (BSD-3-Clause). Each run had one made-up secret and asked for dom and accessibility only. I searched the whole output for the secret in many spellings. Each cell names the artifacts that still held it.

Form on the page main 4999e4be2
This issue's repro: text with &, link with %20 dom and accessibility
&, <, > or " in text, attribute, title or textarea (8 forms) dom
%20, %2F, + or lower-case hex in a link (5 forms) dom and accessibility
One secret with all of those, in text, link and field dom and accessibility
No-break space in text, or in a label with a zero-width space (2 forms) dom and accessibility
" and \ in a label dom and accessibility
: and ' in a label accessibility
Three spaces in text accessibility
JSON \" in an inline script block dom

Rows 2 to 9 hold 20 forms, the same ones as my earlier comment. Row 1 is one run with the two secrets from this issue. Each row matches the result on c1217e711 (#1577).

The merged fix 6edd5cf04 (#1590) changed BrowserScenarioSecrets.redact and the storage capture. It picks a redaction marker that does not contain the secret. It does not add escaped spellings to the dom or accessibility text. The issue's repro gives the same output as on c1217e711.

Still open: #1611 and #1612.

Not run

  • Storage, network bodies and events.
  • Connect mode, headed mode and the MCP path.
  • Other encodings, such as base64 and Unicode escapes.
  • Other systems and other Chromium versions.
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • Node: 24.18.0
  • Head: 4999e4be2
  • Build: 154.0.8037.57

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6103413411 --> @CassieFox23 commented on GitHub (Oct 10, 2026): **TL;DR:** On main 4999e4be2 (#1651), all 20 forms still keep the declared secret in `dom`, `accessibility` or both. That matches my earlier run on c1217e711 (#1577). #1611 and #1612 are still open. ### Results I used `capture-browser-scenario` on headless Chromium 154 ([BSD-3-Clause](https://chromium.googlesource.com/chromium/src/+/main/LICENSE)). Each run had one made-up secret and asked for `dom` and `accessibility` only. I searched the whole output for the secret in many spellings. Each cell names the artifacts that still held it. | Form on the page | main 4999e4be2 | |---|---| | This issue's repro: text with `&`, link with `%20` | `dom` and `accessibility` | | `&`, `<`, `>` or `"` in text, attribute, title or textarea (8 forms) | `dom` | | `%20`, `%2F`, `+` or lower-case hex in a link (5 forms) | `dom` and `accessibility` | | One secret with all of those, in text, link and field | `dom` and `accessibility` | | No-break space in text, or in a label with a zero-width space (2 forms) | `dom` and `accessibility` | | `"` and `\` in a label | `dom` and `accessibility` | | `: ` and `'` in a label | `accessibility` | | Three spaces in text | `accessibility` | | JSON `\"` in an inline script block | `dom` | Rows 2 to 9 hold 20 forms, the same ones as my earlier comment. Row 1 is one run with the two secrets from this issue. Each row matches the result on c1217e711 (#1577). The merged fix 6edd5cf04 (#1590) changed `BrowserScenarioSecrets.redact` and the storage capture. It picks a redaction marker that does not contain the secret. It does not add escaped spellings to the `dom` or `accessibility` text. The issue's repro gives the same output as on c1217e711. Still open: #1611 and #1612. ### Not run - Storage, network bodies and events. - Connect mode, headed mode and the MCP path. - Other encodings, such as `base64` and Unicode escapes. - Other systems and other Chromium versions. <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - Node: 24.18.0 - Head: 4999e4be2 - Build: `154.0.8037.57` </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@CassieFox23 commented on GitHub (Oct 11, 2026):

Thanks, morluto, for explaining the decision on #1611. I accept the scope.

Thanks, Aliciawque and Y0UW1SH, for the fixes in #1611 and #1612, and for the work to keep them current.

🤖 Generated with Claude Code (Opus 5.5) 🦊

<!-- gh-comment-id:6104236463 --> @CassieFox23 commented on GitHub (Oct 11, 2026): Thanks, morluto, for [explaining the decision](https://github.com/morluto/rea/pull/1611#issuecomment-6104198404) on #1611. I accept the scope. Thanks, Aliciawque and Y0UW1SH, for the fixes in #1611 and #1612, and for the work to keep them current. 🤖 Generated with Claude Code (Opus 5.5) 🦊
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#546
No description provided.