Context
Operator surfaced the code-scanning queue for triage. Two of the open alerts — 41 and 42, rule js/identity-replacement — are a genuine defect rather than a false positive, and this ticket carries only those two. The src/Neo.mjs prototype alerts (62/63) are deliberate prototype enrichment and want a documented dismissal, not a fix; alert 64 is a separate rule in a separate file. Neither is in scope here.
This was verified before filing, not inferred from the alert. CodeQL named the line; the reproduction below is mine.
Live latest-open sweep: checked latest 20 open issues at 2026-08-21T15:5x UTC; gh search issues for identity-replacement OR templateBuildProcessor returned zero; corpus grep over resources/content/issues/** for templateBuildProcessor returned zero; A2A claim sweep over the last 30 messages found no overlapping lane. No equivalent exists.
The Problem
Both sites intend to escape single quotes before wrapping a static chunk in a single-quoted JS string literal. The comment at :119 says so outright:
return `'${part.replace(/'/g, "\'" )}'`;In a double-quoted JS string, "\'" is not an escape sequence — it evaluates to a bare '. The call therefore replaces every apostrophe with itself. The intent and the behaviour have diverged silently since the line was written, and nothing downstream notices because the failure needs an apostrophe in static template text adjacent to a dynamic part.
Proven, not asserted:
input : "it's here"
output: "it's here"
IDENTITY: true
Reproduced through the real exported function, not a reconstruction of it — processHtmlTemplateLiteral(['<div>it\'s ', ' here</div>'], ['count']), i.e. the build-time equivalent of html`<div>it's ${count} here</div>` :
NEO_CODE_BLOCK_2
The emitted expression is 'it's ' + (count) + ' here'. With a positive control:
NEO_CODE_BLOCK_3
The control matters — it establishes that the correctly-escaped form parses, so the failure is attributable to the escaping and not to the surrounding expression shape.
Realistic impact is build-time breakage, not a runtime vulnerability. Template text is author-controlled source, so this is a correctness defect that happens to wear an injection-shaped rule name: any component author who writes a contraction in static markup next to an interpolation gets generated code that will not parse. The security label is here so the alert-triage path finds the ticket, not as a severity claim.
The Architectural Reality
buildScripts/util/templateBuildProcessor.mjs converts tagged html template literals into Neo VDOM at build time. Static chunks and ##__NEO_EXPR__ placeholders are joined into a single + addition chain, which is later emitted as source. The two flawed sites are the only places that construct a quoted literal from a raw static chunk:
buildScripts/util/templateBuildProcessor.mjs:120 — the text-node chain
buildScripts/util/templateBuildProcessor.mjs:182 — the attribute-value chain
Both are reached from the sole export, processHtmlTemplateLiteral at :312. The two paths are independent: :120 handles children, :182 handles attributes, so a fix to one does not cover the other and each needs its own coverage.
The Fix
Replace the identity replacement with a real escape at both sites, so the emitted literal carries a backslash:
NEO_CODE_BLOCK_4
A backslash in the static chunk must also survive, which the current code does not handle either — escape backslashes before quotes, or the emitted literal mis-nests on input containing \. The fix should handle both characters in the correct order rather than only the character CodeQL named.
Prefer routing both sites through one shared helper rather than repeating the expression twice: the defect exists in duplicate precisely because the logic was inlined at two call sites, and a third chain would inherit it again.
Acceptance Criteria
Out of Scope
- Alerts 62/63 (
js/prototype-pollution-utility, src/Neo.mjs:563,565) — deliberate prototype enrichment; wants a documented dismissal decision from @tobiu, tracked separately
- Alert 64 (
js/shell-command-injection-from-environment, buildScripts/build/highlightJs.mjs:53) — different rule, different file, separate analysis
- Any broader rework of the expression-chain emitter
Avoided Traps
Fixing only the character CodeQL named. The alert points at the quote; the backslash case is the same defect one character over and would remain after a literal-minded fix, with the alert closed and the bug alive.
Asserting the emitted string instead of parsing it. A test that compares the output to an expected string passes for any consistent-but-wrong escaping. Parsing is the property that actually matters, which is why the AC names it.
Related
Surfaced from the code-scanning queue during triage alongside 62/63/64.
Retrieval Hint: templateBuildProcessor identity replacement single quote escaping build
Origin Session ID: 752da6ac-a6c3-447f-8847-1da4ce49deb8
Decision Record impact: none — build-script correctness, no ADR authority touched. Structure-map gate: N/A, no ai/ surface and no file placement.
Context
Operator surfaced the code-scanning queue for triage. Two of the open alerts — 41 and 42, rule
js/identity-replacement— are a genuine defect rather than a false positive, and this ticket carries only those two. Thesrc/Neo.mjsprototype alerts (62/63) are deliberate prototype enrichment and want a documented dismissal, not a fix; alert 64 is a separate rule in a separate file. Neither is in scope here.This was verified before filing, not inferred from the alert. CodeQL named the line; the reproduction below is mine.
Live latest-open sweep: checked latest 20 open issues at 2026-08-21T15:5x UTC;
gh search issuesforidentity-replacement OR templateBuildProcessorreturned zero; corpus grep overresources/content/issues/**fortemplateBuildProcessorreturned zero; A2A claim sweep over the last 30 messages found no overlapping lane. No equivalent exists.The Problem
Both sites intend to escape single quotes before wrapping a static chunk in a single-quoted JS string literal. The comment at
:119says so outright:// Escape single quotes for the string literal part of the chain. return `'${part.replace(/'/g, "\'" )}'`;In a double-quoted JS string,
"\'"is not an escape sequence — it evaluates to a bare'. The call therefore replaces every apostrophe with itself. The intent and the behaviour have diverged silently since the line was written, and nothing downstream notices because the failure needs an apostrophe in static template text adjacent to a dynamic part.Proven, not asserted:
Reproduced through the real exported function, not a reconstruction of it —
processHtmlTemplateLiteral(['<div>it\'s ', ' here</div>'], ['count']), i.e. the build-time equivalent ofhtml`<div>it's ${count} here</div>`:NEO_CODE_BLOCK_2
The emitted expression is
'it's ' + (count) + ' here'. With a positive control:NEO_CODE_BLOCK_3
The control matters — it establishes that the correctly-escaped form parses, so the failure is attributable to the escaping and not to the surrounding expression shape.
Realistic impact is build-time breakage, not a runtime vulnerability. Template text is author-controlled source, so this is a correctness defect that happens to wear an injection-shaped rule name: any component author who writes a contraction in static markup next to an interpolation gets generated code that will not parse. The
securitylabel is here so the alert-triage path finds the ticket, not as a severity claim.The Architectural Reality
buildScripts/util/templateBuildProcessor.mjsconverts taggedhtmltemplate literals into Neo VDOM at build time. Static chunks and##__NEO_EXPR__placeholders are joined into a single+addition chain, which is later emitted as source. The two flawed sites are the only places that construct a quoted literal from a raw static chunk:buildScripts/util/templateBuildProcessor.mjs:120— the text-node chainbuildScripts/util/templateBuildProcessor.mjs:182— the attribute-value chainBoth are reached from the sole export,
processHtmlTemplateLiteralat:312. The two paths are independent::120handles children,:182handles attributes, so a fix to one does not cover the other and each needs its own coverage.The Fix
Replace the identity replacement with a real escape at both sites, so the emitted literal carries a backslash:
NEO_CODE_BLOCK_4
A backslash in the static chunk must also survive, which the current code does not handle either — escape backslashes before quotes, or the emitted literal mis-nests on input containing
\. The fix should handle both characters in the correct order rather than only the character CodeQL named.Prefer routing both sites through one shared helper rather than repeating the expression twice: the defect exists in duplicate precisely because the logic was inlined at two call sites, and a third chain would inherit it again.
Acceptance Criteria
:120and:182emit a correctly-escaped single-quoted literal; both go through one shared escaping helper rather than duplicated inline expressions\and'in the same chunk both survivedevfor the text-node path:182independently — a fix to one site must not green the other's armOut of Scope
js/prototype-pollution-utility,src/Neo.mjs:563,565) — deliberate prototype enrichment; wants a documented dismissal decision from @tobiu, tracked separatelyjs/shell-command-injection-from-environment,buildScripts/build/highlightJs.mjs:53) — different rule, different file, separate analysisAvoided Traps
Fixing only the character CodeQL named. The alert points at the quote; the backslash case is the same defect one character over and would remain after a literal-minded fix, with the alert closed and the bug alive.
Asserting the emitted string instead of parsing it. A test that compares the output to an expected string passes for any consistent-but-wrong escaping. Parsing is the property that actually matters, which is why the AC names it.
Related
Surfaced from the code-scanning queue during triage alongside 62/63/64.
Retrieval Hint:
templateBuildProcessor identity replacement single quote escaping buildOrigin Session ID: 752da6ac-a6c3-447f-8847-1da4ce49deb8
Decision Record impact: none — build-script correctness, no ADR authority touched. Structure-map gate: N/A, no
ai/surface and no file placement.