Context: Frontend; the reader audience is largely minors, so any XSS carries the in-memory session token.
Problem: Uploaded EPUB/book HTML is rendered with Angular's sanitizer explicitly bypassed → stored XSS. Two more DOM-XSS sinks compound it, and browser-side Keycloak admin APIs mean any XSS escalates to identity-provider takeover.
Evidence:
projects/spikersoft/src/app/_components/epub-reader/epub-reader.component.ts:43 (bypassSecurityTrustHtml), rendered via epub-content-pane.component.html:16 ([innerHTML])
libraries/features/dev-tools-diagram/src/lib/nodes/node-classes.ts:418-428 (new Function)
_components/terminal/terminal.component.ts:179-180 (innerHTML of user input)
Fix: Render EPUB HTML through the default sanitizer (or DOMPurify server-side to an allowlist); sandbox custom-code nodes in a worker with no DOM/token; escape terminal output; move Keycloak admin behind the backend (relates to #298).
Acceptance criteria: No bypassSecurityTrust* on uploaded content; no new Function/innerHTML on user input; the SPA token never carries realm-admin scope.
Effort: M · Related: #298 (keycloak-admin specialization).
**Context:** Frontend; the reader audience is largely minors, so any XSS carries the in-memory session token.
**Problem:** Uploaded EPUB/book HTML is rendered with Angular's sanitizer explicitly bypassed → stored XSS. Two more DOM-XSS sinks compound it, and browser-side Keycloak *admin* APIs mean any XSS escalates to identity-provider takeover.
**Evidence:**
- `projects/spikersoft/src/app/_components/epub-reader/epub-reader.component.ts:43` (`bypassSecurityTrustHtml`), rendered via `epub-content-pane.component.html:16` (`[innerHTML]`)
- `libraries/features/dev-tools-diagram/src/lib/nodes/node-classes.ts:418-428` (`new Function`)
- `_components/terminal/terminal.component.ts:179-180` (`innerHTML` of user input)
- `libraries/keycloak-admin/src/lib/services/keycloak-base.service.ts:18,24-33` (browser-side Keycloak admin REST)
**Fix:** Render EPUB HTML through the default sanitizer (or DOMPurify server-side to an allowlist); sandbox custom-code nodes in a worker with no DOM/token; escape terminal output; move Keycloak admin behind the backend (relates to #298).
**Acceptance criteria:** No `bypassSecurityTrust*` on uploaded content; no `new Function`/`innerHTML` on user input; the SPA token never carries realm-admin scope.
**Effort:** M · Related: #298 (keycloak-admin specialization).
spikerj
added the agentic label 2026-07-05 20:24:38 +00:00
htmlContent is server-supplied EPUB page HTML (ReaderService.getEpubPage() → content.html) rendered from user-uploaded books, so any <script>/onerror=/javascript: in a malicious EPUB executes → stored XSS on a minors platform.
Why it can't be a one-line "just sanitize": the bypass exists to preserve EPUB typography. Angular's built-in DomSanitizer.sanitize(SecurityContext.HTML, …) (and plain [innerHTML]) strips <style> blocks and many attributes, so dropping the bypass would regress book formatting — a core feature on a reading platform. The correct fix is DOMPurify with an EPUB-safe profile (allow structural + style/class, USE_PROFILES:{html:true}, FORBID_TAGS:['script'], FORBID_ATTR:[/^on/]), then bind the purified string. That adds a dependency and, critically, needs visual render verification against real EPUBs — not just a build/lint pass.
Other bypassSecurityTrustHtml usages audited — safe, leave alone:
contact.component.ts:147 — hardcoded SVG icon literals (trusted, not user input).
dev-tools-x86-playground/.../disassembly-panel.component.ts — disassembler output in a dev tool (controlled generation).
Recommendation: treat as a proper (reviewed) task: add DOMPurify + EPUB-safe config, a unit test asserting <script>/on* are stripped while <style>/class survive, and a manual render check of a formatted EPUB. Also worth confirming whether the backend sanitizes EPUB HTML at ingest (defense-in-depth — belongs regardless). Flagging rather than shipping a blind sanitizer change that could break the reader.
### Triage — the EPUB sanitizer bypass
The live XSS sink is `projects/spikersoft/src/app/_components/epub-reader/epub-reader.component.ts:43`:
```ts
readonly safeHtml = computed<SafeHtml>(() => this.sanitizer.bypassSecurityTrustHtml(this.htmlContent()));
```
`htmlContent` is **server-supplied EPUB page HTML** (`ReaderService.getEpubPage()` → `content.html`) rendered from user-uploaded books, so any `<script>`/`onerror=`/`javascript:` in a malicious EPUB executes → **stored XSS on a minors platform**.
**Why it can't be a one-line "just sanitize":** the bypass exists to preserve EPUB typography. Angular's built-in `DomSanitizer.sanitize(SecurityContext.HTML, …)` (and plain `[innerHTML]`) **strips `<style>` blocks and many attributes**, so dropping the bypass would regress book formatting — a core feature on a reading platform. The correct fix is **DOMPurify with an EPUB-safe profile** (allow structural + `style`/`class`, `USE_PROFILES:{html:true}`, `FORBID_TAGS:['script']`, `FORBID_ATTR:[/^on/]`), then bind the purified string. That adds a dependency and, critically, needs **visual render verification** against real EPUBs — not just a build/lint pass.
**Other `bypassSecurityTrustHtml` usages audited — safe, leave alone:**
- `contact.component.ts:147` — hardcoded SVG icon literals (trusted, not user input).
- `dev-tools-x86-playground/.../disassembly-panel.component.ts` — disassembler output in a dev tool (controlled generation).
**Recommendation:** treat as a proper (reviewed) task: add DOMPurify + EPUB-safe config, a unit test asserting `<script>`/`on*` are stripped while `<style>`/`class` survive, and a manual render check of a formatted EPUB. Also worth confirming whether the **backend** sanitizes EPUB HTML at ingest (defense-in-depth — belongs regardless). Flagging rather than shipping a blind sanitizer change that could break the reader.
Audited the two remaining DOM sinks — severities differ from the ticket; scoping so the real one gets the effort
Terminal innerHTML (terminal.component.ts:179-180) — downgrade to near-nil; recommend closing this sub-item.
Two independent reasons it isn't a live vector:
The #t2 target is a <textarea> (template confirms). innerHTML on a textarea is parsed in RCDATA — <img onerror=…>/<script> become literal text, they do not execute. (It also means the terminal's own {red}→<span> coloring never renders — the component is half-broken.)
TerminalComponent (app-terminal) is Storybook-only — no route/template in the app uses it (only *.stories.ts + *.spec.ts). The x86-playground has its own separate terminal-panel.
So there's no reachable XSS here. I'd rather flag it honestly than ship an escaping "fix" that implies a vuln that isn't there. (Happy to add the escape as pure defense-in-depth if you want the AC literally satisfied, but it's cosmetic.)
new Function(code) custom-code node (node-classes.ts:418-428) — this is the real one; keep it.
The dev-tools-diagram feature is routed/shipped (routes.ts:361, lazy DiagramComponent). The "Custom Code" node runs user text via new Function(code)() in the page context — full DOM + in-memory session token.
Execution is explicit (execution.service.play() → node.execute), not auto-on-load, and the file-import paths I found load JSON data payloads into nodes, not a serialized graph — so today's vector is closest to self-exec (you run code you typed), escalating to stored code-exec only if a full diagram graph (with a Custom Code node) can be shared+imported then run. Worth confirming whether graph import/share exists or is planned.
Correct fix per the ticket (sandbox in a Worker with no DOM/token) is an M-sized refactor (worker bundling under Nx/Angular, structured-clone of the result, timeout for infinite loops) that changes node behavior (code loses DOM access — appropriate for a "compute a result" node) and wants a browser/build smoke test, so I'm not blind-shipping it.
Recommendation: close the terminal sub-item as not-a-vuln; keep item 2 (custom-code) + item 1 (EPUB, DOMPurify — see prior comment) + item 4 (keycloak-admin behind backend, #298). I can implement the Worker sandbox for the custom-code node on a go-ahead if someone can run a browser check that the diagram still executes custom-code nodes after the change.
### Audited the two remaining DOM sinks — severities differ from the ticket; scoping so the real one gets the effort
**Terminal `innerHTML` (`terminal.component.ts:179-180`) — downgrade to near-nil; recommend closing this sub-item.**
Two independent reasons it isn't a live vector:
1. The `#t2` target is a **`<textarea>`** (template confirms). `innerHTML` on a textarea is parsed in **RCDATA** — `<img onerror=…>`/`<script>` become literal text, they do **not** execute. (It also means the terminal's own `{red}`→`<span>` coloring never renders — the component is half-broken.)
2. `TerminalComponent` (`app-terminal`) is **Storybook-only** — no route/template in the app uses it (only `*.stories.ts` + `*.spec.ts`). The x86-playground has its own separate `terminal-panel`.
So there's no reachable XSS here. I'd rather flag it honestly than ship an escaping "fix" that implies a vuln that isn't there. (Happy to add the escape as pure defense-in-depth if you want the AC literally satisfied, but it's cosmetic.)
**`new Function(code)` custom-code node (`node-classes.ts:418-428`) — this is the real one; keep it.**
- The `dev-tools-diagram` feature **is routed/shipped** (`routes.ts:361`, lazy `DiagramComponent`). The "Custom Code" node runs user text via `new Function(code)()` in the page context — full DOM + in-memory session token.
- Execution is **explicit** (`execution.service.play()` → `node.execute`), not auto-on-load, and the file-import paths I found load **JSON data payloads into nodes**, not a serialized graph — so today's vector is closest to **self-exec** (you run code you typed), escalating to **stored code-exec only if a full diagram graph (with a Custom Code node) can be shared+imported then run**. Worth confirming whether graph import/share exists or is planned.
- Correct fix per the ticket (sandbox in a Worker with no DOM/token) is an **M-sized refactor** (worker bundling under Nx/Angular, structured-clone of the result, timeout for infinite loops) that changes node behavior (code loses DOM access — appropriate for a "compute a result" node) and **wants a browser/build smoke test**, so I'm not blind-shipping it.
**Recommendation:** close the terminal sub-item as not-a-vuln; keep item 2 (custom-code) + item 1 (EPUB, DOMPurify — see prior comment) + item 4 (keycloak-admin behind backend, #298). I can implement the Worker sandbox for the custom-code node on a go-ahead if someone can run a browser check that the diagram still executes custom-code nodes after the change.
Per the prescribed fix: uploaded EPUB HTML now goes through DOMPurify with an EPUB-safe profile before bypassSecurityTrustHtml (which now wraps an already-purified string). New pure epub-html-sanitizer.ts + 6 unit tests pinning the contract — <script>/on*/javascript:/iframe·object·embed·form stripped, while <style> blocks + class/style + structural markup are preserved (FORCE_BODY: true stops a leading <style> being hoisted to <head> and dropped). Adds the dompurify dep. nx lint+build+test spikersoft (1869) green.
Needs your ~2-min visual check that a formatted EPUB still renders (typography preserved) before merge. CSS caveat:url("javascript:…") inside a <style> survives DOMPurify but is inert in modern browsers; full CSS isolation would need a sandboxed iframe (out of scope).
Remaining sub-items unchanged: item 2 (new Function custom-code node → Worker sandbox, M-sized, needs a browser smoke test — happy to take on a go-ahead); item 3 (terminal innerHTML) recommend closing as not-a-vuln (textarea RCDATA + Storybook-only); item 4 (Keycloak-admin behind backend, ties to #298).
**Item 1 (EPUB sanitizer bypass) implemented — spikersoft-angular PR #148 (open, for review).**
Per the prescribed fix: uploaded EPUB HTML now goes through DOMPurify with an EPUB-safe profile before `bypassSecurityTrustHtml` (which now wraps an already-purified string). New pure `epub-html-sanitizer.ts` + 6 unit tests pinning the contract — `<script>`/`on*`/`javascript:`/`iframe`·`object`·`embed`·`form` stripped, while `<style>` blocks + `class`/`style` + structural markup are preserved (`FORCE_BODY: true` stops a leading `<style>` being hoisted to `<head>` and dropped). Adds the `dompurify` dep. `nx lint`+`build`+`test spikersoft` (1869) green.
Needs your ~2-min visual check that a formatted EPUB still renders (typography preserved) before merge. **CSS caveat:** `url("javascript:…")` inside a `<style>` survives DOMPurify but is inert in modern browsers; full CSS isolation would need a sandboxed iframe (out of scope).
**Remaining sub-items unchanged:** item 2 (`new Function` custom-code node → Worker sandbox, M-sized, needs a browser smoke test — happy to take on a go-ahead); item 3 (terminal `innerHTML`) recommend closing as **not-a-vuln** (textarea RCDATA + Storybook-only); item 4 (Keycloak-admin behind backend, ties to #298).
Audited the dev-tools-diagram feature for any path that could persist/import/share a full graph (which would turn the new Function Custom Code node into stored code-exec against another user):
No graph serialize/deserialize exists.diagram.component.ts's only localStorage use is loadNumber/persistNumber (panel-size prefs), not the rete graph.
The file-import (FileReader.readAsText) + IndexedDB (QuickTypeJSONStorage) paths load QuickType JSON data into nodes for type generation — not a serialized graph, and never the Custom Code node's code control.
The node's code comes solely from the live InputControl the user types into in their own session (node-classes.ts:418-428).
Conclusion: the Custom Code node is self-XSS only — you run JS you typed, in your own session, on the /tools dev-diagram route. There is no attacker→victim vector (nothing an attacker authors is stored or delivered to another user). Severity is low, not the stored-XSS class of item 1.
Recommendation (your call): given that, the Worker-sandbox refactor (item 2) is a behavior change (Custom Code loses DOM access) for low security benefit, and its worker-bundling core can't be unit-verified. Options: (a) accept as documented dev-tool behavior and close item 2; (b) lightweight mitigation — leave a comment/guard noting it's intentional self-exec; (c) still sandbox it for defense-in-depth on a go-ahead (I'd extract a unit-testable orchestrator + you run the browser smoke test). I'd lean (a)/(b) unless a graph share/import feature is planned — say which and I'll execute.
**Item 2 threat-model resolved (code analysis) — it's self-exec only, no stored/shared vector.**
Audited the `dev-tools-diagram` feature for any path that could persist/import/share a full graph (which would turn the `new Function` Custom Code node into *stored* code-exec against another user):
- **No graph serialize/deserialize exists.** `diagram.component.ts`'s only `localStorage` use is `loadNumber`/`persistNumber` (panel-size prefs), not the rete graph.
- The file-import (`FileReader.readAsText`) + IndexedDB (`QuickTypeJSONStorage`) paths load **QuickType JSON *data* into nodes** for type generation — not a serialized graph, and never the Custom Code node's `code` control.
- The node's `code` comes solely from the live `InputControl` the user types into in their own session (`node-classes.ts:418-428`).
**Conclusion:** the Custom Code node is **self-XSS only** — you run JS you typed, in your own session, on the `/tools` dev-diagram route. There is no attacker→victim vector (nothing an attacker authors is stored or delivered to another user). Severity is **low**, not the stored-XSS class of item 1.
**Recommendation (your call):** given that, the Worker-sandbox refactor (item 2) is a **behavior change** (Custom Code loses DOM access) for **low** security benefit, and its worker-bundling core can't be unit-verified. Options: (a) **accept as documented dev-tool behavior** and close item 2; (b) **lightweight mitigation** — leave a comment/guard noting it's intentional self-exec; (c) **still sandbox it** for defense-in-depth on a go-ahead (I'd extract a unit-testable orchestrator + you run the browser smoke test). I'd lean (a)/(b) unless a graph share/import feature is planned — say which and I'll execute.
Item 1 (EPUB sanitizer bypass) merged to master — spikersoft-angular PR #148. Uploaded EPUB HTML now sanitized through DOMPurify (EPUB-safe profile) before bypassSecurityTrustHtml; 6 unit tests pin the contract. The headline stored-XSS sink is closed.
Keeping this ticket open for the remaining sub-items:
Item 2 (new Function custom-code node): threat model resolved to self-exec only (no graph share/import path) — recommend accept/close or lightweight guard rather than the Worker-sandbox refactor. Your call (see prior comment).
Item 4 (Keycloak-admin behind backend): separate, ties to #298.
If you're happy with the recommendations on 2/3, this reduces to item 4 and can likely be narrowed to that.
**Item 1 (EPUB sanitizer bypass) merged to `master`** — spikersoft-angular PR #148. Uploaded EPUB HTML now sanitized through DOMPurify (EPUB-safe profile) before `bypassSecurityTrustHtml`; 6 unit tests pin the contract. The headline stored-XSS sink is closed.
Keeping this ticket **open** for the remaining sub-items:
- **Item 2** (`new Function` custom-code node): threat model resolved to **self-exec only** (no graph share/import path) — recommend accept/close or lightweight guard rather than the Worker-sandbox refactor. Your call (see prior comment).
- **Item 3** (terminal `innerHTML`): **not-a-vuln** (textarea RCDATA + Storybook-only) — recommend close.
- **Item 4** (Keycloak-admin behind backend): separate, ties to #298.
If you're happy with the recommendations on 2/3, this reduces to item 4 and can likely be narrowed to that.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context: Frontend; the reader audience is largely minors, so any XSS carries the in-memory session token.
Problem: Uploaded EPUB/book HTML is rendered with Angular's sanitizer explicitly bypassed → stored XSS. Two more DOM-XSS sinks compound it, and browser-side Keycloak admin APIs mean any XSS escalates to identity-provider takeover.
Evidence:
projects/spikersoft/src/app/_components/epub-reader/epub-reader.component.ts:43(bypassSecurityTrustHtml), rendered viaepub-content-pane.component.html:16([innerHTML])libraries/features/dev-tools-diagram/src/lib/nodes/node-classes.ts:418-428(new Function)_components/terminal/terminal.component.ts:179-180(innerHTMLof user input)libraries/keycloak-admin/src/lib/services/keycloak-base.service.ts:18,24-33(browser-side Keycloak admin REST)Fix: Render EPUB HTML through the default sanitizer (or DOMPurify server-side to an allowlist); sandbox custom-code nodes in a worker with no DOM/token; escape terminal output; move Keycloak admin behind the backend (relates to #298).
Acceptance criteria: No
bypassSecurityTrust*on uploaded content; nonew Function/innerHTMLon user input; the SPA token never carries realm-admin scope.Effort: M · Related: #298 (keycloak-admin specialization).
Triage — the EPUB sanitizer bypass
The live XSS sink is
projects/spikersoft/src/app/_components/epub-reader/epub-reader.component.ts:43:htmlContentis server-supplied EPUB page HTML (ReaderService.getEpubPage()→content.html) rendered from user-uploaded books, so any<script>/onerror=/javascript:in a malicious EPUB executes → stored XSS on a minors platform.Why it can't be a one-line "just sanitize": the bypass exists to preserve EPUB typography. Angular's built-in
DomSanitizer.sanitize(SecurityContext.HTML, …)(and plain[innerHTML]) strips<style>blocks and many attributes, so dropping the bypass would regress book formatting — a core feature on a reading platform. The correct fix is DOMPurify with an EPUB-safe profile (allow structural +style/class,USE_PROFILES:{html:true},FORBID_TAGS:['script'],FORBID_ATTR:[/^on/]), then bind the purified string. That adds a dependency and, critically, needs visual render verification against real EPUBs — not just a build/lint pass.Other
bypassSecurityTrustHtmlusages audited — safe, leave alone:contact.component.ts:147— hardcoded SVG icon literals (trusted, not user input).dev-tools-x86-playground/.../disassembly-panel.component.ts— disassembler output in a dev tool (controlled generation).Recommendation: treat as a proper (reviewed) task: add DOMPurify + EPUB-safe config, a unit test asserting
<script>/on*are stripped while<style>/classsurvive, and a manual render check of a formatted EPUB. Also worth confirming whether the backend sanitizes EPUB HTML at ingest (defense-in-depth — belongs regardless). Flagging rather than shipping a blind sanitizer change that could break the reader.Audited the two remaining DOM sinks — severities differ from the ticket; scoping so the real one gets the effort
Terminal
innerHTML(terminal.component.ts:179-180) — downgrade to near-nil; recommend closing this sub-item.Two independent reasons it isn't a live vector:
#t2target is a<textarea>(template confirms).innerHTMLon a textarea is parsed in RCDATA —<img onerror=…>/<script>become literal text, they do not execute. (It also means the terminal's own{red}→<span>coloring never renders — the component is half-broken.)TerminalComponent(app-terminal) is Storybook-only — no route/template in the app uses it (only*.stories.ts+*.spec.ts). The x86-playground has its own separateterminal-panel.So there's no reachable XSS here. I'd rather flag it honestly than ship an escaping "fix" that implies a vuln that isn't there. (Happy to add the escape as pure defense-in-depth if you want the AC literally satisfied, but it's cosmetic.)
new Function(code)custom-code node (node-classes.ts:418-428) — this is the real one; keep it.dev-tools-diagramfeature is routed/shipped (routes.ts:361, lazyDiagramComponent). The "Custom Code" node runs user text vianew Function(code)()in the page context — full DOM + in-memory session token.execution.service.play()→node.execute), not auto-on-load, and the file-import paths I found load JSON data payloads into nodes, not a serialized graph — so today's vector is closest to self-exec (you run code you typed), escalating to stored code-exec only if a full diagram graph (with a Custom Code node) can be shared+imported then run. Worth confirming whether graph import/share exists or is planned.Recommendation: close the terminal sub-item as not-a-vuln; keep item 2 (custom-code) + item 1 (EPUB, DOMPurify — see prior comment) + item 4 (keycloak-admin behind backend, #298). I can implement the Worker sandbox for the custom-code node on a go-ahead if someone can run a browser check that the diagram still executes custom-code nodes after the change.
Item 1 (EPUB sanitizer bypass) implemented — spikersoft-angular PR #148 (open, for review).
Per the prescribed fix: uploaded EPUB HTML now goes through DOMPurify with an EPUB-safe profile before
bypassSecurityTrustHtml(which now wraps an already-purified string). New pureepub-html-sanitizer.ts+ 6 unit tests pinning the contract —<script>/on*/javascript:/iframe·object·embed·formstripped, while<style>blocks +class/style+ structural markup are preserved (FORCE_BODY: truestops a leading<style>being hoisted to<head>and dropped). Adds thedompurifydep.nx lint+build+test spikersoft(1869) green.Needs your ~2-min visual check that a formatted EPUB still renders (typography preserved) before merge. CSS caveat:
url("javascript:…")inside a<style>survives DOMPurify but is inert in modern browsers; full CSS isolation would need a sandboxed iframe (out of scope).Remaining sub-items unchanged: item 2 (
new Functioncustom-code node → Worker sandbox, M-sized, needs a browser smoke test — happy to take on a go-ahead); item 3 (terminalinnerHTML) recommend closing as not-a-vuln (textarea RCDATA + Storybook-only); item 4 (Keycloak-admin behind backend, ties to #298).Item 2 threat-model resolved (code analysis) — it's self-exec only, no stored/shared vector.
Audited the
dev-tools-diagramfeature for any path that could persist/import/share a full graph (which would turn thenew FunctionCustom Code node into stored code-exec against another user):diagram.component.ts's onlylocalStorageuse isloadNumber/persistNumber(panel-size prefs), not the rete graph.FileReader.readAsText) + IndexedDB (QuickTypeJSONStorage) paths load QuickType JSON data into nodes for type generation — not a serialized graph, and never the Custom Code node'scodecontrol.codecomes solely from the liveInputControlthe user types into in their own session (node-classes.ts:418-428).Conclusion: the Custom Code node is self-XSS only — you run JS you typed, in your own session, on the
/toolsdev-diagram route. There is no attacker→victim vector (nothing an attacker authors is stored or delivered to another user). Severity is low, not the stored-XSS class of item 1.Recommendation (your call): given that, the Worker-sandbox refactor (item 2) is a behavior change (Custom Code loses DOM access) for low security benefit, and its worker-bundling core can't be unit-verified. Options: (a) accept as documented dev-tool behavior and close item 2; (b) lightweight mitigation — leave a comment/guard noting it's intentional self-exec; (c) still sandbox it for defense-in-depth on a go-ahead (I'd extract a unit-testable orchestrator + you run the browser smoke test). I'd lean (a)/(b) unless a graph share/import feature is planned — say which and I'll execute.
Item 1 (EPUB sanitizer bypass) merged to
master— spikersoft-angular PR #148. Uploaded EPUB HTML now sanitized through DOMPurify (EPUB-safe profile) beforebypassSecurityTrustHtml; 6 unit tests pin the contract. The headline stored-XSS sink is closed.Keeping this ticket open for the remaining sub-items:
new Functioncustom-code node): threat model resolved to self-exec only (no graph share/import path) — recommend accept/close or lightweight guard rather than the Worker-sandbox refactor. Your call (see prior comment).innerHTML): not-a-vuln (textarea RCDATA + Storybook-only) — recommend close.If you're happy with the recommendations on 2/3, this reduces to item 4 and can likely be narrowed to that.
Board-sweep status (2026-07-22): item 1 merged (EPUB DOMPurify, angular #148). REMAINING: the new-Function node + the other DOM-XSS / keycloak-admin sinks.