| โฆ | โฆ | @@ -148,6 +148,229 @@ const SNAPSHOT_SCRIPT = `(() => { | |
| 331 | + | .toSorted((a, b) => b.form.length - a.form.length) | |
| 332 | + | .map(({ form, word }) => { | |
| 333 | + | const literal = form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| 334 | + | return word ? `(?<![^\\s"])${literal}(?![^\\s"])` : literal; | |
๐ High preview/ServerBrowserPage.ts:334
Short dotted values such as 123 remain visible in snapshot output when adjacent to punctuation: dottedPattern only treats whitespace or " as boundaries, so PIN: 123. and Code (123) are not redacted. Treat punctuation as a boundary while still excluding matches embedded in words.
| 1 | return word ? `(?<![\\p{L}\\p{N}_])${literal}(?![\\p{L}\\p{N}_])` : literal; |
๐ Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/preview/ServerBrowserPage.ts around line 334: |
| 2 | |
| 3 | Short dotted values such as `123` remain visible in snapshot output when adjacent to punctuation: `dottedPattern` only treats whitespace or `"` as boundaries, so `PIN: 123.` and `Code (123)` are not redacted. Treat punctuation as a boundary while still excluding matches embedded in words. |
Fixed in cd319961cf4. Short values now match between any non-letter/digit/underscore characters (Unicode-aware), so PIN: 123. and (123) are masked and 1234 is left alone. Covered by a test.
| โฆ | โฆ | @@ -173,36 +396,63 @@ export const captureViewport = async ( | |
| 188 | 416 | const generation = state.generation; | |
| 189 | - | const [page, tree, data] = await Promise.all([ | |
| 417 | + | // Awaited after the tree: a capture that fails has finished its tree before a retry starts. | |
| 418 | + | const pageAndScreenshot = Promise.all([ | |
๐ High preview/ServerBrowserPage.ts:418
The returned visibleText can expose an old password value: SNAPSHOT_SCRIPT captures it before the password reads, so if the value changes from A to B (or is cleared) during reader setup, both reads agree on the new value and A is not masked. Capture visibleText between the before and after reads so the comparison covers the text being returned.
๐ Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/preview/ServerBrowserPage.ts around line 418: |
| 2 | |
| 3 | The returned `visibleText` can expose an old password value: `SNAPSHOT_SCRIPT` captures it before the password reads, so if the value changes from A to B (or is cleared) during reader setup, both reads agree on the new value and A is not masked. Capture `visibleText` between the before and after reads so the comparison covers the text being returned. |
Fixed in cd319961cf4. Page text is now captured after the first password read and before the second, so a value that changes during either read fails the comparison and the snapshot is retried.
| โฆ | โฆ | @@ -211,6 +461,8 @@ export const snapshot = async (input: { | |
| 212 | 462 | return { | |
| 213 | 463 | ...page, | |
| 464 | + | // Text that holds a value would otherwise show where the tree masked it. | |
| 465 | + | visibleText: mask(page.visibleText), | |
๐ High preview/ServerBrowserPage.ts:465
A password that crosses the 20,000-character cutoff is returned with its visible prefix unmasked (for example, secre). SNAPSHOT_SCRIPT truncates visibleText before mask(page.visibleText) runs, so the full-value pattern cannot match the truncated text; apply masking before truncation.
๐ Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/preview/ServerBrowserPage.ts around line 465: |
| 2 | |
| 3 | A password that crosses the 20,000-character cutoff is returned with its visible prefix unmasked (for example, `secre`). `SNAPSHOT_SCRIPT` truncates `visibleText` before `mask(page.visibleText)` runs, so the full-value pattern cannot match the truncated text; apply masking before truncation. |
Fixed in cd319961cf4. Page text is masked first and truncated to 20,000 characters afterward, so a value crossing the cut leaves no prefix. Covered by a test.
| โฆ | โฆ | @@ -148,6 +148,229 @@ const SNAPSHOT_SCRIPT = `(() => { | |
| 165 | + | const dots = getComputedStyle(element).getPropertyValue("-webkit-text-security"); | |
| 166 | + | if (element.type !== "password" && dots === "none") continue; | |
| 167 | + | for (const value of [element.value, element.defaultValue]) | |
| 168 | + | values.push(value.replace(/[\\u200b\\u00ad]/g, "").trim().replace(/\\s+/g, " ")); | |
๐ High preview/ServerBrowserPage.ts:167
A password containing a non-breaking space can appear unredacted in visibleText, exposing the complete secret. This line stores only the whitespace-normalized form, while SNAPSHOT_SCRIPT preserves the rendered text; collect the raw value as a mask candidate as well.
| 1 | for (const value of [element.value, element.defaultValue]) { |
| 2 | const raw = value.replace(/[\\u200b\\u00ad]/g, ""); |
| 3 | values.push(raw, raw.trim().replace(/\\s+/g, " ")); |
| 4 | } |
๐ Reply "fix it for me" or copy this AI Prompt for your agent:
| 1 | In file @apps/server/src/preview/ServerBrowserPage.ts around lines 167-168: |
| 2 | |
| 3 | A password containing a non-breaking space can appear unredacted in `visibleText`, exposing the complete secret. This line stores only the whitespace-normalized form, while `SNAPSHOT_SCRIPT` preserves the rendered text; collect the raw value as a mask candidate as well. |
Fixed in cd319961cf4. Each value is now kept as a candidate both raw and normalized (deduped), so text with non-breaking spaces is masked. Covered by a test.