Files
jason.woltje 4ff4f44c98 lead: commit researcher's untracked evidence (1406 files: queue-101 queue-105 queue-128 queue-134 queue-143 queue-148 queue-183 queue-228)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:14 -06:00

278 lines
23 KiB
Markdown

# Row 148 / #1642: library choice (step 1)
Researcher, 2026-10-16T17:48Z. Nothing under `public/` changed; no claim made; nothing in the repository was
installed or run. Downloads live in `/mnt/storage/scratch/researcher/q148-lib/` (each in its own new directory,
extracted with `tar`, no scripts run).
## Answer
**Toast UI Editor 3.2.2 is the only candidate that comes close, and it does not pass your grep rule as written.**
Your decision, Sage:
1. The built file contains two `Function("return this")` call sites, one written `new Function(…)` (details below). Both sit behind
`globalThis` / `window` / `self` checks and did not run in my CSP probe. A literal grep still finds them. If you rule that a
dead fallback is acceptable, I vendor it. If you rule the literal rule is absolute, **no candidate fits** and the fallback
(textarea plus a preview built from repository code) applies.
2. Its stylesheet embeds 8 `data:` images. The Console CSP has no `img-src`, so it falls back to `default-src 'self'` and
blocks them (toolbar rendered empty, 1 violation). A CSP change is not allowed, so I would ship a **derived stylesheet**
with the images extracted into files served from `'self'. That is a modification of the upstream CSS, so say yes or no.
If both are yes, the vendor set is the minified variant below.
## The candidate
| Field | Value |
|---|---|
| Name | Toast UI Editor (`@toast-ui/editor`) |
| Version | 3.2.2 (built Fri Feb 17 2023; last release) |
| License | MIT, Copyright (c) 2020 NHN Cloud Corp. Header in the bundle says `@license MIT`. |
| Bundled third-party | DOMPurify 2.3.3 (Apache-2.0 / MPL-2.0 dual), ProseMirror (MIT), tui-code-snippet (MIT) |
| Mode | Real WYSIWYG, with a Markdown tab toggle. Output is Markdown (`getMarkdown()`). |
| Upstream LICENSE | `https://raw.githubusercontent.com/nhn/tui.editor/master/LICENSE`, 1,072 B, sha256 `1c7c070da956f0c3122883c1b4eb706b1b819971082ccde001fe2d241d90e3c0` |
### Provenance gap
- The npm tarball (`@toast-ui/editor@3.2.2`, sha512 `dist.integrity` verified) has **no LICENSE file** and **no standalone browser
bundle**. Its `dist/toastui-editor.js` expects ProseMirror externals and is not usable in a plain `<script>`.
- The standalone bundle `toastui-editor-all(.min).js` comes from `uicdn.toast.com/editor/3.2.2/`, downloaded once. It is not an
npm-verified artifact. I compared it to the npm dist: 98.62% of the npm `dist` lines appear verbatim in the all.js
(the remainder is the UMD wrapper). The CSS from the CDN is byte-identical to the npm CSS (sha256 `c0ceb967…`).
- The LICENSE file comes from GitHub `master`, not from the release tag.
### Files I would vendor (recommended: minified variant, derived CSS)
Directory `public/vendor/toastui-editor-3.2.2/` (shown relative to it):
| sha256 | bytes | file |
|---|---:|---|
| f50e1b7c0fc4e5d9a1ccd0d8be78cb3a950ccb3bf676fbf1627810c76aeaedd8 | 534289 | toastui-editor-all.min.js (unmodified upstream) |
| 61aabe63e8ddb86e18d7560bd883c3fd60f8adc14851fcad3eecd472c1854dd2 | 27790 | toastui-editor.min.css (**derived**) |
| 1a5dd9ba2158c9f579545ece60c293b496249231a1ed3aea6143c6d646217275 | 23915 | img/1a5dd9ba.png (extracted) |
| e15d7afe083df2f3c835a681a905cce197f388dd5bda89788caef03d42ec0217 | 53818 | img/e15d7afe.png (extracted) |
| 9fc65db774689965f7dfc9b83390d748ed8640414078ebc39ba534c2433ac569 | 506 | img/9fc65db7.svg (extracted) |
| 0b41c43d3595f11277fd39f5e31723095ec70aefa28561145723eae234f75dda | 166 | img/0b41c43d.svg (extracted) |
| ccdcf3ad97fec299e13a5b57abc474c6c64b1228d3335c9963356d3b435c9a59 | 342 | img/ccdcf3ad.svg (extracted) |
| b2eb3ec2ef047acf17091ba8eab9c8cf34f9752c370c349c518c92a9ac725cd6 | 257 | img/b2eb3ec2.svg (extracted) |
| 1c7c070da956f0c3122883c1b4eb706b1b819971082ccde001fe2d241d90e3c0 | 1072 | LICENSE |
Total: **642,155 bytes** (641,083 of code and images plus the 1,072 B LICENSE). `vendor.sha256` would list these paths.
That is 9 files plus `vendor.sha256` itself. serve.mjs serves from an explicit route table, so it needs a route for each file the
page loads (the js, the css, the 6 images: 8 routes; LICENSE and `vendor.sha256` only if you want them served). Row 148 owns serve.mjs.
The derived CSS is `node harness/split-css.mjs` over the upstream `toastui-editor.min.css` (upstream sha256
`c70e24c68fefc205e8e504edc07fd6a5efd3044a623b4be7e3ac16cc8a736ed9`, 165,438 B). The script is copied next to this file as
`split-css.mjs`. It base64-decodes each `url(data:image/(png|svg+xml);base64,…)` into `img/<sha8>.<ext>`, rewrites the
reference to `url(img/<sha8>.<ext>)`, and throws on any other `data:`. The output has 0 `data:`, 0 `@import`, 0 `@font-face`;
its only `url()` references are the 6 image files. The SVG files contain no `<script>` and no `href`.
Readable alternative (unminified, derived), if you want a diffable file: `toastui-editor-all.js` 1,461,692 B
(sha256 `0d8c202706e3ea8d91c28307c445b55b746cf985d0994d70951b257ba207c8d9`), `toastui-editor.css` 32,958 B
(sha256 `5e0a615d740f7f0ddb44f8dfab6f37d5fc32d34b6beb924f3876429b87fb570f`), 6 images (4 svg with different bytes), total 1,574,902 B
before the LICENSE (its 6 images differ from the min set only in the 4 svg files). I recommend the minified one: 2.4x smaller, same code, same Function sites.
## Grep results (on the files I would vendor; JS is byte-identical in both variants of each pair)
Method: node regex over the file read as latin1 (`ctx.mjs` / one-line node), because this shell's grep is ugrep and fails on
long context patterns. Counts on `toastui-editor-all.min.js` (the unminified file gives the same except where noted):
| Pattern | Hits |
|---|---:|
| `eval(` | 0 |
| `new Function` | **1** |
| `Function(` (not preceded by identifier or `.`; includes the `new Function(` site) | **2** |
| `.constructor("…")` | 0 |
| `importScripts`, `new Worker` | 0 |
| `XMLHttpRequest`, `fetch(`, `sendBeacon`, `WebSocket` | 0 |
| `document.write` | 0 |
| `setTimeout("…")` / `setInterval("…")` string form | 0 |
| `insertAdjacentHTML` | 3 (5 in the unminified file; I did not trace what feeds them) |
The two `Function` sites:
1. `@25145`: `n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}()`
webpack global shim. `globalThis` exists in every supported browser, so the first branch returns.
2. `@488455`: `…typeof window&&window.Math===Math?window:Function("return this")()` lodash-style root detection, last in a chain after
`globalThis`/`self`/`window`.
Under the Console CSP these would throw an `EvalError` and fall into a `catch` if ever reached; the editor never reaches them in a browser.
External URLs in the bundle (distinct): three XML namespace strings (`w3.org`), a `prosemirror.net` docs link in a message, and
`https://www.google-analytics.com/collect`. The last is the **analytics ping**, sent through an `<img>` by default.
`usageStatistics` defaults to `true`; row 148 must pass `usageStatistics: false`. With the CSP it would be blocked anyway, but the
CSP violation would be noise and the code path writes `localStorage`.
## Runtime probe (the Console CSP, verbatim, on a scratch server at 127.0.0.1)
Page loads the vendored files over `script-src 'self'`, `style-src 'self' 'unsafe-inline'`, no `img-src`, `connect-src 'self'`.
A `securitypolicyviolation` listener plus `error` and `unhandledrejection` listeners recorded everything.
| Variant | Editor constructs | CSP violations | JS errors | Toolbar |
|---|---|---:|---:|---|
| As shipped (upstream CSS with `data:` images) | yes | **1** (`img-src`, `data:`) | 0 | icons blank |
| Derived CSS, unminified | yes | 0 | 0 | 17 of 17 icons render |
| Derived CSS, **minified** | yes | 0 | 0 | 17 of 17 icons have an image |
Network for the derived pages: `html`, `css`, `probe.js`, the JS bundle, `run.js`, one PNG. Nothing left the origin.
Behaviour check on the minified derived page, `setMarkdown` then Markdown tab then WYSIWYG tab then `getMarkdown()`:
- Headings, task list, fenced code, quote, strike, inline code and link survived.
- **Rewrites untouched content**: the bullet markers `-` came back as `*`. Row 148's save will turn a hand-written `-` list into a `*`
list on any edit. That is Git diff noise on the north-star spec, not data loss. I did not test tables, nested lists, or long files in round trip.
- Sanitizing: a pasted `<script>` block was dropped; `<img onerror=…>` came back as `<img src="x">`; `window.__pwn` stayed unset. Raw HTML in a spec is therefore not preserved.
- The task-list checkbox picture (the SVGs) loads from `'self'`.
Not tested: the image dialog, link popup, table context menu, code-block language picker, paste from the clipboard, IME input,
and any file over a few kB. The probe ran in a Chromium instance only.
## Rejected
| Candidate | Version | Reason |
|---|---|---|
| Wysimark standalone | 3.0.20 | Fetches emoji data from `cdn.jsdelivr.net` and calls `api.portive.com`; Prism worker; `Function("return this")`; 2.9 MB; last release 2023-09. Needs CSP changes. |
| Vditor | 4.0.0 | `Function('"use strict";return (…)')()` helper evaluates strings; `importScripts` Blob worker; injects inline `<script>` text (blocked by `script-src 'self'`); loads from unpkg by default; 3.7 MB Lute core. |
| EasyMDE | 2.21.0 | No eval, but not WYSIWYG (CodeMirror source plus preview, no better than the fallback). Fetches spell-checker dictionaries from `cdn.jsdelivr.net` and Font Awesome from maxcdn by default. |
| Milkdown / Crepe | 7.22.3 | **Not downloaded.** Registry metadata only: no prebuilt browser bundle, needs a bundler and dozens of transitive packages; the vendored file would be our own build output, not a published artifact. |
| MDXEditor | 4.3.3 | **Not downloaded.** Same reason, and it is React-only. |
| bytemd | 1.22.0 | **Not downloaded.** Same reason; it is also a split-pane source editor. |
| TinyMCE | 8.10.0 | **Not downloaded.** Not a Markdown editor, and the license field reads "SEE LICENSE IN license.md", not plainly permissive. |
## Risks to weigh
- Toast UI's last release is 2023-02-17 and it bundles DOMPurify 2.3.3, which is old; the sanitizer is the thing that stops pasted
HTML, so a known DOMPurify bypass would matter. I did not check DOMPurify 2.3.3 advisories.
- The standalone bundle is not npm-verifiable (see provenance gap). The sha256 values above pin what I downloaded, and no more.
- The stylesheet is modified from upstream, and `vendor.sha256` will pin the derived files, not upstream's.
## Fallback
If you rule against Toast UI: a `<textarea>` plus a rendered preview from a small Markdown renderer written in the repository
(headings, lists, emphasis, code, links, tables), no vendored code, no CSP change, no license file. It loses WYSIWYG editing.
That is the brief's own fallback.
## Cleanup
Scratch server on 127.0.0.1:18148 and the isolated browser page are stopped after this write-up. All downloads stay in scratch.
# Step 1b: DOMPurify 2.3.3 advisories and Toast UI provenance
Researcher, 2026-10-16T17:58Z. Same scratch rules as step 1: nothing under `public/` changed, no claim, nothing pushed, no credential
used. Only unauthenticated reads (GitHub advisory API, OSV API, npm registry, raw GitHub files). Evidence lives in
`/mnt/storage/scratch/researcher/q148-1b/` (`adv/`, `tag/`, `dompurify-meta/`) and the harness pages in
`/mnt/storage/scratch/researcher/q148-lib/harness/site/`. No downloaded script was run; the PoC test suite from the DOMPurify fix
commit was read, not executed.
## 1. DOMPurify 2.3.3: advisories
Method: GitHub Advisory API for `cure53/DOMPurify` (all 30 advisories saved in `adv/dompurify-advisories.json`) and the OSV query
for `npm/dompurify@2.3.3` (`adv/osv-dompurify-2.3.3.json`, 20 hits). I then read each advisory text and checked each against what
Toast actually calls. The two sources disagree on ranges: OSV marks 20 as `introduced=0`, GitHub gives lower bounds only for some.
Many of the 20 describe options or code that I did not find in 2.3.3 (see the "not reachable" rows). I list the 20 OSV hits; the
other 10 GitHub advisories have a lower bound above 2.3.3 (3.0.x or later) and are left out.
### What Toast passes to DOMPurify (call-site inspection of `toastui-editor-all.js`)
- One wrapper: `sanitizeHTML(html, options)` -> `purify_default().sanitize(html, {ADD_TAGS: <array>, ADD_ATTR: ['rel','target','hreflang','type'], FORBID_TAGS: [...], ...options})`.
- Four module-level callers: `@1183440` (`img[src]` parseDOM, `RETURN_DOM_FRAGMENT: true`), `@1193888` (`a[href]` parseDOM, same),
`@1224346` (`getMatchedAttributeValue`, string result into `innerHTML`), `@1240027` and `@1241321` (markdown-to-WYSIWYG inline/block
HTML convertor, string result).
- Never set: `IN_PLACE`, `USE_PROFILES`, `SAFE_FOR_TEMPLATES`, `setConfig()`, `CUSTOM_ELEMENT_HANDLING`, Trusted Types options, function-form
`ADD_TAGS`/`ADD_ATTR`. `CUSTOM_ELEMENT_HANDLING` does not exist in the 2.3.3 source at all (0 hits). I found no DOMPurify hook
registration (the single `addHook(` hit is the editor's own events hook, `@1318242`).
- Output goes back in through `innerHTML` (into a `div`) or through ProseMirror `parseDOM`.
### Advisories that matter for 2.3.3
| GHSA | CVE | Sev (CVSS) | Affects 2.3.3 | Fixed in | Source | Reachable via Toast's config | Stopped by `script-src 'self'`, no unsafe-inline |
|---|---|---|---|---|---|---|---|
| GHSA-gx9m-whjm-85jf | CVE-2024-47875 | critical (10.0) | yes, `<2.5.0` | 2.5.0, 3.1.3 | https://github.com/advisories/GHSA-gx9m-whjm-85jf | **Yes.** Nesting-based mXSS, default config. I verified on 2.3.3 in Chromium that there is no nesting-depth limit (2.5.0 added it); `purify-depth.html`. | **Script effects: yes.** Markup it yields is not filtered by `FORBID_TAGS`/`ADD_TAGS` because the mutation happens after the filter. Inline handlers, `javascript:` URLs and inline `<script>` do not run under the CSP (tested, section 2). HTML injection (links, inline-style overlays) is not stopped. |
| GHSA-mmhx-hmjr-r674 | CVE-2024-45801 | high (7.0) | yes, `<2.5.4` | 2.5.4, 3.1.3 | https://github.com/advisories/GHSA-mmhx-hmjr-r674 | **Partly.** It bypasses the depth check added in 2.5.0, and 2.3.3 has no depth check, so it is covered by the row above. The prototype-pollution half needs a pollution primitive in the page; I found none in Toast and did not look elsewhere. | Same as above for the nesting half; pollution half is independent of the CSP. |
| GHSA-p3vf-v8qc-cwcr | CVE-2024-48910 | critical (9.1) | yes, `<2.4.2` | 2.4.2 | https://github.com/advisories/GHSA-p3vf-v8qc-cwcr | **Only with a separate prototype-pollution bug** in the page. No such primitive found in Toast; I did not audit the Console. | No (it tampers with DOMPurify's own config; the follow-on effect is the same HTML injection). |
### Advisories whose OSV range includes 2.3.3 but whose code path Toast does not use
All "Reachable: no" claims below come from the Toast call-site list above, not from running a PoC. For each, "affects 2.3.3" is
the OSV range claim (`introduced=0`); I did not test whether 2.3.3 actually has the flaw, because the option is never set.
| GHSA | CVE | Sev | Fixed in | Source | Why not reachable via Toast |
|---|---|---|---|---|---|
| GHSA-39q2-94rc-95cp | CVE-2026-65903 | low | 3.4.0 | https://github.com/advisories/GHSA-39q2-94rc-95cp | needs function-form `ADD_TAGS`; Toast passes an array |
| GHSA-h7mw-gpvr-xq4m | CVE-2026-41240 | medium | 3.4.0 | https://github.com/advisories/GHSA-h7mw-gpvr-xq4m | same |
| GHSA-cjmm-f4jc-qw8r | CVE-2026-65912 | medium | 3.3.2 | https://github.com/advisories/GHSA-cjmm-f4jc-qw8r | needs function-form `ADD_ATTR`; Toast passes an array |
| GHSA-cj63-jhhr-wcxv | CVE-2026-65913 | medium | 3.3.2 | https://github.com/advisories/GHSA-cj63-jhhr-wcxv | needs `USE_PROFILES` plus Array.prototype pollution |
| GHSA-crv5-9vww-q3g8 | CVE-2026-41239 | medium | 3.4.0 | https://github.com/advisories/GHSA-crv5-9vww-q3g8 | needs `SAFE_FOR_TEMPLATES` with `RETURN_DOM`; Toast does not set it |
| GHSA-vhxf-7vqr-mrjg | CVE-2025-26791 | medium | 3.2.4 | https://github.com/advisories/GHSA-vhxf-7vqr-mrjg | needs `SAFE_FOR_TEMPLATES` |
| GHSA-h8r8-wccr-v5f2 | CVE-2026-65914 | medium | 3.3.2 | https://github.com/advisories/GHSA-h8r8-wccr-v5f2 | **Unknown.** GitHub lists only 3.3.1; OSV says from 0. mXSS by re-contextualisation, no option needed. I did not test it on 2.3.3. Treat as possibly reachable; same CSP effect as the first row. |
| GHSA-55q2-fjhq-7xh7 | CVE-2026-75838 | low | 3.4.13 | https://github.com/advisories/GHSA-55q2-fjhq-7xh7 | `IN_PLACE` plus hooks; not set |
| GHSA-6688-9rhm-gjv2 | CVE-2026-109345 | low | 3.4.16 | https://github.com/advisories/GHSA-6688-9rhm-gjv2 | `IN_PLACE`; not set |
| GHSA-hpcv-96wg-7vj8 | CVE-2026-49458 | medium | 3.4.6 | https://github.com/advisories/GHSA-hpcv-96wg-7vj8 | `IN_PLACE`; not set |
| GHSA-r47g-fvhr-h676 | CVE-2026-49459 | medium | 3.4.6 | https://github.com/advisories/GHSA-r47g-fvhr-h676 | `IN_PLACE`; not set |
| GHSA-x4vx-rjvf-j5p4 | CVE-2026-65901 | low | 3.4.7 (OSV gives no fix; GitHub says `<= 3.4.6`) | https://github.com/advisories/GHSA-x4vx-rjvf-j5p4 | `IN_PLACE`; not set |
| GHSA-rp9w-3fw7-7cwq | CVE-2026-49978 | medium | 3.4.7 | https://github.com/advisories/GHSA-rp9w-3fw7-7cwq | `IN_PLACE`; not set |
| GHSA-76mc-f452-cxcm | CVE-2026-65902 | medium | 3.4.7 | https://github.com/advisories/GHSA-76mc-f452-cxcm | needs a DOMPurify hook mutating allow-lists; Toast registers none |
| GHSA-cmwh-pvxp-8882 | CVE-2026-65898 | low | 3.4.11 | https://github.com/advisories/GHSA-cmwh-pvxp-8882 | needs `setConfig()`; Toast does not call it |
| GHSA-c2j3-45gr-mqc4 | CVE-2026-66010 | low | 3.4.12 | https://github.com/advisories/GHSA-c2j3-45gr-mqc4 | `CUSTOM_ELEMENT_HANDLING`; absent from 2.3.3 |
| GHSA-vxr8-fq34-vvx9 | CVE-2026-65899 | low | 3.4.9 | https://github.com/advisories/GHSA-vxr8-fq34-vvx9 | Trusted Types policy; not set |
Not applicable (GitHub lower bound above 2.3.3): GHSA-87xg-pxx2-7hvx, GHSA-v9jr-rg53-9pgp, GHSA-4w3q-35jp-p934, GHSA-9p3w-6h5p-cv75,
GHSA-gvmj-g25r-r7wr, GHSA-c6gr-qvv4-5v3f, GHSA-mrv2-3pqr-j5jr, GHSA-cg2g-xh64-v4q7,
GHSA-p98j-92pf-mc4p. (These are all 3.x-only; I did not read their text beyond the ranges.) OSV has no entry for `@toast-ui/editor@3.2.2`
itself (`adv/osv-toastui-3.2.2.json` is `{}`). That is absence of a record, not a clean bill: Toast is unmaintained since 2023-02.
### 2. Does the Console CSP stop the effect? (empirical, Chromium, the exact CSP from `serve.mjs:209`)
Page: `csp-effects.html/js` on the scratch server. Each payload was inserted with `innerHTML` into a same-origin page under the CSP,
which is what Toast does with sanitizer output.
| Payload | Result under the CSP |
|---|---|
| `<img src=x onerror=...>` / `<svg onload=...>` inline handlers | **blocked** (script-src has no unsafe-inline), violation reported |
| `<a href="javascript:...">` click | **blocked** |
| `<iframe srcdoc="<script>...">` | **blocked** (srcdoc inherits the policy) |
| `<img src=https://other.example/x>` | **blocked** (img-src falls back to `default-src 'self'`), so no beacon via images |
| `<link rel=stylesheet href=https://...>`, CSS `url(https://...)` | **blocked** |
| `<form action=https://other.example>` | **blocked** (`form-action 'self'`) |
| Plain `<a href=https://other.example>` | **not blocked**: user can be sent off-site by a click |
| `<meta http-equiv=refresh content="0;url=https://...">` | **not blocked** by the CSP. Toast's `FORBID_TAGS` drops `meta`, so it only matters after an mXSS bypass. |
| Inline `style=` (full-page overlay, fake UI) | **not blocked**: `style-src 'unsafe-inline'` is in the Console CSP |
| `<form action=/same-origin-path method=post>` | **not blocked** (`form-action 'self'`); `form` is in Toast's `FORBID_TAGS` but not after mXSS. Whether a same-origin POST can do harm depends on Console routes I did not review. |
So the CSP turns the critical advisories from script execution into HTML injection. It does **not** make 2.3.3 safe: the Console
shows a human content that an agent can write (agents author the `docs/` files this editor opens), so link redirection, overlay
spoofing and same-origin form POST remain possible after an mXSS bypass.
## 3. Provenance
| Question | Finding |
|---|---|
| Tag | `editor@3.2.2` (GitHub tag name; `@toast-ui/editor@3.2.2` returns 404). Commit `9b94c04231d4600b42347ff1b99d9813e9becf67`. |
| GitHub release | No release object for this tag; tag only (`tag/tags-list.json`, `tag/releases.json` checked). |
| `toastui-editor-all.min.js` in tag or release assets | **No.** The repo tree at the tag (`tag/tree.json`) has no `dist/` and no bundle; no release assets. |
| Reproducible build | **Not reproduced.** The build is a webpack run via upstream scripts and `npm install` of its dev dependencies. Both are disallowed here (no install, no running downloaded scripts). I cannot say a build from the tag would produce f50e1b7c… |
| Second source | None found. The bundle exists only at `uicdn.toast.com/editor/3.2.2/` (CDN of the vendor); the npm tarball has `dist/toastui-editor.js` (not standalone). I compared line content earlier: 98.62% of the npm dist lines occur verbatim in the all.js. |
| CSS | Upstream CDN CSS equals the npm CSS byte for byte (`c0ceb967…` unminified). The tag's own style sources (`tag/src-contents.css`, `tag/src-editor.css`) were fetched only to compare the images. |
| Images | The 2 PNGs extracted from the CSS match the PNGs in the tag source byte for byte. The 4 SVGs in the **unminified** CSS match the tag source; the 4 SVGs in the **minified** CSS differ in bytes (the minifier rewrote them). The minified set is therefore not byte-identical to source for SVGs; it is what the CDN served. |
| LICENSE at tag vs master | **Identical.** `tag/LICENSE-tag-editor-3.2.2` sha256 `1c7c070da956f0c3122883c1b4eb706b1b819971082ccde001fe2d241d90e3c0`, 1072 B, same as the master copy in step 1. Row 148 can vendor it from the tag with a recorded source URL. |
Net: the vendored JS is pinned to a CDN download with no independent confirmation. A reviewer can re-fetch the same URL and compare
the sha256 and that is all; there is no upstream-signed hash and no reproducible build I could run.
## 4. If a 1b finding is bad
It is bad, in the sense that the critical nesting mXSS (GHSA-gx9m-whjm-85jf) is reachable with Toast's default config and the CSP only converts it into HTML injection.
One line: I would not rely on `customHTMLSanitizer` alone, because four internal call sites (img/a `parseDOM`, `getMatchedAttributeValue`,
and the markdown-to-WYSIWYG HTML convertor) call the bundled 2.3.3 directly and ignore it; so the choice is to splice DOMPurify 2.5.9 (latest 2.x, same major as the
bundled 2.3.3, so the `sanitize()` options Toast uses stay the same; it fixes both 2024 advisories, and no 2.x-range advisory above lists a fix later than 2.5.4) into
the bundle's module slot (the vendored JS then becomes derived like the CSS and loses its single upstream sha256; **untested**, I
would download 2.5.9 into its own scratch dir, check npm integrity, then run the nesting test, the Toast integration page and the
0-violation CSP check before any build), or fall back to textarea plus preview if that cannot be verified or a derived JS file is refused.
DOMPurify 3.4.17 is the alternative splice, with more API drift from 2.3.3 to check.
## Not verified
- A splice of a newer DOMPurify into the bundle (not built, not run).
- Whether 2.3.3 is actually vulnerable to GHSA-h8r8-wccr-v5f2 and the other OSV-only entries; I judged them by call-site reachability.
- Whether the Console has a same-origin POST route that a forged `<form>` could hit.
- A pollution primitive anywhere in the Console page.
- Anything in Firefox or Safari; all runtime tests were in one Chromium.