Skip to content

Fix regex escapes collapsing in emitted client-side scripts - #33

Merged
gbyo merged 1 commit into
mainfrom
gibby/fix-template-regex-escapes
Aug 11, 2026
Merged

gbyo merged 1 commit into
mainfrom
gibby/fix-template-regex-escapes

Conversation

@gbyo

@gbyo gbyo commented Aug 11, 2026

Copy link
Copy Markdown
Member

Problem

Three regex literals live inside TypeScript template literals that emit client-side JavaScript. A single backslash is not a recognized escape sequence, so it collapses when the template is evaluated — the browser receives the bare character instead of the character class.

node -e "console.log(\`x.replace(/\D/g,'')\`)"   # -> x.replace(/D/g,'')

This is invisible to tsc: the TypeScript compiles cleanly, and only the served page is wrong.

What was broken

Site Emitted to browser Effect
library.ts kioskHtml /D/g Kiosk pairing PIN sanitizer stripped the letter D while keeping hyphens, spaces and letters
library.ts manageHtml (×2) /^d{4}-d{2}-d{2}$/ CSV export fully broken — rejected every real date, always threw "Choose a start and end date."
admin-ui-v2.ts adminHtml /(d{4})(d{4})/ Pairing PIN rendered 12345678 instead of 1234 5678 (cosmetic)

Fix

Escape the backslash so the emitted page receives \d / \D. Three lines changed.

Deliberately left unchanged

Not every single backslash in a template literal is a bug — some depend on the collapse:

  • admin-ui-v3.ts:7, admin-ui-v6.ts:10, admin-ui-v6.ts:18 (×3) — \n collapses to a real newline, which is exactly what's needed as a statement separator in the emitted JS. Escaping these to \\n would emit a literal two-char \n and cause a syntax error.
  • library.ts escapeHtml — /[&<>\"]/g collapses to /[&<>"]/g; inside a character class " needs no escape, so the served regex is already correct.

access-router.ts, admin.ts, admin-v2/v3/v4.ts are clean — all their backslashes are ordinary server-side TS regexes.

How this was audited

Grep can't distinguish an emitted-script backslash from ordinary TS code, so the sweep used a lexer tracking template-literal nesting, ${} substitutions, strings, comments and regex literals, reporting each backslash's run length and enclosing template opener. All 13 src/*.ts files, 25 backslashes total, each classification confirmed by reading its context.

Verification

  • tsc --noEmit on the repo tsconfig — clean
  • Rendered kioskHtml(), manageHtml(), adminHtml() and asserted on the served markup: correct patterns present, broken forms absent
  • Ran the emitted sanitizer: "12-34 567" → "1234567", "abc12345678" → "12345678"
  • Ran the emitted date regex: 2026-08-11 → true, dddd-dd-dd → false
  • All 3 inline script blocks parse cleanly under vm.Script; zero literal \n sequences leaked into output

Follow-up worth considering

This class of bug is invisible to tsc and will recur, since the codebase emits a lot of client-side JS through template literals. The audit scanner is ~60 lines and could run in CI as a guard — the rule being "an odd backslash run in template text is suspect unless followed by n".

🤖 Generated with Claude Code

These regex literals live inside TypeScript template literals that emit
client-side JavaScript. A single backslash is not a recognized escape
sequence, so it collapses when the template is evaluated and the browser
receives the bare character instead of the character class.

  library.ts kioskHtml      /\D/g                  -> /D/g
  library.ts manageHtml     /^\d{4}-\d{2}-\d{2}$/  -> /^d{4}-d{2}-d{2}$/
  admin-ui-v2.ts adminHtml  /(\d{4})(\d{4})/       -> /(d{4})(d{4})/

Effects: the kiosk pairing PIN sanitizer stripped the letter D while
keeping hyphens, spaces and letters; the manage-page CSV export rejected
every real date and always threw "Choose a start and end date."; the
pairing PIN rendered without its group separator.

Escaping the backslash makes the emitted page receive \d and \D.

Left unchanged deliberately: the \n sequences in admin-ui-v3.ts and
admin-ui-v6.ts rely on this collapse to produce real newlines as
statement separators, and library.ts escapeHtml's /[&<>\"]/g collapses
to /[&<>"]/g, which is already the intended regex.

Verified by rendering kioskHtml(), manageHtml() and adminHtml() and
asserting on the served markup: the correct patterns are present, the
broken ones absent, the emitted sanitizer and date validator behave
correctly, and all inline script blocks still parse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the area:source Changes to application source code label Aug 11, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
flow e6f1895 Aug 11 2026, 12:01 PM

@gbyo
gbyo merged commit 1117a0d into main Aug 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:source Changes to application source code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant