fix(security): @nuxtjs/mdc ^0.22.2 (CVE-2026-63671) — hold for 3.0 #59

Open
A-Guevara wants to merge 1 commit from fix/mdc-cve-2026-63671 into main
Owner

Recommendation: don't merge this on its own — land it with the 3.0 release. Reasons and the landing checklist are at the bottom.

What

Moves the layer's @nuxtjs/mdc range from ^0.21.1 to ^0.22.2, clearing CVE-2026-63671 / GHSA-mxm6-v9r6-r94c (HIGH). Below 0.22.1, mdc's parse-time sanitizer lets javascript: on an SVG <a xlink:href> and a data:text/html src through, and parseMarkdown enables raw HTML by default. Everything tux renders through MDC (TuxProse, TuxMarkdownEditor, /design/*, /changelog, /install) sits behind that sanitizer, and so does every consumer that extends the layer.

Supersedes Renovate's #58, which floors at ^0.22.0 (0.22.0 is still vulnerable) and stays red on both trivy gates: its lockfile nests a vulnerable 0.21.1 under the @tti/tti-ux@2.0.0 that npm installs for packages/react's peer dependency.

Changes (3 files)

  • package.json
    • dependencies["@nuxtjs/mdc"]: ^0.21.1 → ^0.22.2. 0.22.2 is the fix plus a follow-up that also validates bound (:href) and @event attribute names.
    • overrides["@nuxtjs/mdc"]: "$@nuxtjs/mdc", with a "//" note. It only shapes this repo's own install (npm ignores a dependency's overrides): it moves the peer-installed @tti/tti-ux@2.0.0's copy onto the root range, leaving one mdc in the tree.
  • package-lock.json, regenerated with npm 12.0.2. npm 11.12.1 does not propagate the override through the workspace peer and keeps the nested 0.21.1 (tried the $ reference, a literal range and two scoped forms). The npm 12 lockfile is exactly the npm 11 one minus that nested entry. Against main it changes only the mdc entry, plus two stale 2.0.0 → 2.2.0 version fields npm re-synced (root and packages/react).
  • CHANGELOG.md: an ## [Unreleased] → ### Security entry.

Verification

check result
trivy fs, HIGH/CRITICAL (the baseline gate's command) 5 → 4: CVE-2026-63671 cleared, nothing new
npm ci --dry-run accepted by npm 11.12.1, 11.19.0 (Node 24, the Pages deploy) and 12.0.2 (the runner); the only install-set change from main is mdc 0.21.1 → 0.22.2
parse all 112 tracked .md files with 0.21.1 and 0.22.2 identical ASTs, nothing stripped by either
advisory vectors both survive 0.21.1 and are stripped by 0.22.2, as is a bound :href="javascript:…"; https links, /docs/… links and <img src="https://…"> are untouched
Pages build (NUXT_PAGES=1 nuxt generate) green. The 36 MDC-rendered pages match main's build except /changelog, which gains exactly the 21 lines of the new entry. Two builds of unchanged main differ on 22 pages (inlined icon CSS, payload key order), so any other page diff is build noise
nuxt typecheck, eslint . clean
vitest (root) identical to main: 11 files / 102 tests pass; the same 3 files time out on main too (below)
@tti/tti-ux-react tests, run directly 4/4 pass

Pre-existing on main — not caused by this PR

  • The gate stays red on 4 other HIGHs: @tiptap/core 3.28.0, js-yaml 4.3.1, sharp 0.35.3, svgo 4.0.2. They're the same four landscape cleared on 2026-09-15 (nis/landscape b62a1dd). @tiptap/core is a direct editor dependency here, so it wants a real bump of the @tiptap/* set rather than an override.
  • tux-chart-donut, tux-chart-scatter and tux-treemap .nuxt.test.ts time out in setupNuxt (the 10 s hook) on main as well. Because npm test is vitest run && npm run test -w @tti/tti-ux-react, the react workspace tests are silently skipped whenever the root run fails.
  • npm 10 rejects main's lockfile (Missing: cac / commander), as deploy-pages.yml documents.
  • Under npm 11, npm ls flags the peer copy's mdc edge as invalid; npm 12 shows it deduped. npm ls already exits non-zero on main over cac/commander, and nothing in CI runs it.

Why hold it for 3.0

  • A 2.x patch would reach almost nobody. landscape, yard, tti-ai-studio and tti-ai-studio-sampler pin github:ttitamu/tti-ux#v1.7.0, so they can only take the layer's fix through the move to 3.0 anyway. atlas pins @tti/tti-ux at exactly 2.0.0. It is the one consumer a 2.2.1 could help, since that stays inside 2.x, but it doesn't render markdown itself and the override below covers it today.

  • The range change is something every consumer inherits. Riding the major means one dependency migration instead of two.

  • Until 3.0 ships, each consumer covers itself with the one-line override "@nuxtjs/mdc": "$@nuxtjs/mdc" (plus a direct ^0.22.2 if it declares mdc itself). Default-branch status as of 2026-09-21:

    • landscape: done (nis/landscape ba24548).
    • yard, tti-ai-studio: only on unmerged feature branches.
    • atlas, tti-ai-studio-sampler: not yet.

    The sampler renders LLM output through MDC. So does helm2, which uses mdc directly rather than through tux. Those two are the real exposures, and neither is fixed by this PR.

When landing with 3.0

  • Move packages/react's peer to ^3.0.0 in the same release. Once 3.0.0 is published and the lockfile refreshed, the peer copy carries ^0.22.2 itself, and the override and its "//" note can go.
  • Fold this ## [Unreleased] entry into the 3.0 section when cutting the release.
  • Re-run trivy. If the four transitive findings above are handled by then, the gate goes green.
  • The GitHub mirror's main carries three brand-logo commits (561c641..03923db) that aren't on Forgejo main; reconcile them before the release push, since deploy-pages.yml builds from GitHub main.
> **Recommendation: don't merge this on its own — land it with the 3.0 release.** Reasons and the landing checklist are at the bottom. ## What Moves the layer's `@nuxtjs/mdc` range from `^0.21.1` to `^0.22.2`, clearing **CVE-2026-63671 / [GHSA-mxm6-v9r6-r94c](https://github.com/advisories/GHSA-mxm6-v9r6-r94c)** (HIGH). Below 0.22.1, mdc's parse-time sanitizer lets `javascript:` on an SVG `<a xlink:href>` and a `data:text/html` `src` through, and `parseMarkdown` enables raw HTML by default. Everything tux renders through MDC (`TuxProse`, `TuxMarkdownEditor`, `/design/*`, `/changelog`, `/install`) sits behind that sanitizer, and so does every consumer that extends the layer. Supersedes Renovate's #58, which floors at `^0.22.0` (0.22.0 is still vulnerable) and stays red on both trivy gates: its lockfile nests a vulnerable `0.21.1` under the `@tti/tti-ux@2.0.0` that npm installs for `packages/react`'s peer dependency. ## Changes (3 files) - **`package.json`** - `dependencies["@nuxtjs/mdc"]`: `^0.21.1` → `^0.22.2`. 0.22.2 is the fix plus a follow-up that also validates bound (`:href`) and `@event` attribute names. - `overrides["@nuxtjs/mdc"]: "$@nuxtjs/mdc"`, with a `"//"` note. It only shapes this repo's own install (npm ignores a dependency's overrides): it moves the peer-installed `@tti/tti-ux@2.0.0`'s copy onto the root range, leaving one mdc in the tree. - **`package-lock.json`**, regenerated with **npm 12.0.2**. npm 11.12.1 does not propagate the override through the workspace peer and keeps the nested 0.21.1 (tried the `$` reference, a literal range and two scoped forms). The npm 12 lockfile is exactly the npm 11 one minus that nested entry. Against `main` it changes only the mdc entry, plus two stale `2.0.0` → `2.2.0` version fields npm re-synced (root and `packages/react`). - **`CHANGELOG.md`**: an `## [Unreleased]` → `### Security` entry. ## Verification | check | result | |---|---| | trivy fs, HIGH/CRITICAL (the baseline gate's command) | **5 → 4**: CVE-2026-63671 cleared, nothing new | | `npm ci --dry-run` | accepted by npm 11.12.1, 11.19.0 (Node 24, the Pages deploy) and 12.0.2 (the runner); the only install-set change from `main` is mdc 0.21.1 → 0.22.2 | | parse all 112 tracked `.md` files with 0.21.1 and 0.22.2 | identical ASTs, nothing stripped by either | | advisory vectors | both survive 0.21.1 and are stripped by 0.22.2, as is a bound `:href="javascript:…"`; https links, `/docs/…` links and `<img src="https://…">` are untouched | | Pages build (`NUXT_PAGES=1 nuxt generate`) | green. The 36 MDC-rendered pages match `main`'s build except `/changelog`, which gains exactly the 21 lines of the new entry. Two builds of unchanged `main` differ on 22 pages (inlined icon CSS, payload key order), so any other page diff is build noise | | `nuxt typecheck`, `eslint .` | clean | | vitest (root) | identical to `main`: 11 files / 102 tests pass; the same 3 files time out on `main` too (below) | | `@tti/tti-ux-react` tests, run directly | 4/4 pass | ## Pre-existing on `main` — not caused by this PR - **The gate stays red on 4 other HIGHs**: `@tiptap/core` 3.28.0, `js-yaml` 4.3.1, `sharp` 0.35.3, `svgo` 4.0.2. They're the same four landscape cleared on 2026-09-15 (nis/landscape b62a1dd). `@tiptap/core` is a direct editor dependency here, so it wants a real bump of the `@tiptap/*` set rather than an override. - `tux-chart-donut`, `tux-chart-scatter` and `tux-treemap` `.nuxt.test.ts` time out in `setupNuxt` (the 10 s hook) on `main` as well. Because `npm test` is `vitest run && npm run test -w @tti/tti-ux-react`, the react workspace tests are silently skipped whenever the root run fails. - npm 10 rejects `main`'s lockfile (`Missing: cac` / `commander`), as `deploy-pages.yml` documents. - Under npm 11, `npm ls` flags the peer copy's mdc edge as invalid; npm 12 shows it deduped. `npm ls` already exits non-zero on `main` over cac/commander, and nothing in CI runs it. ## Why hold it for 3.0 - **A 2.x patch would reach almost nobody.** landscape, yard, tti-ai-studio and tti-ai-studio-sampler pin `github:ttitamu/tti-ux#v1.7.0`, so they can only take the layer's fix through the move to 3.0 anyway. atlas pins `@tti/tti-ux` at exactly `2.0.0`. It is the one consumer a 2.2.1 could help, since that stays inside 2.x, but it doesn't render markdown itself and the override below covers it today. - The range change is something every consumer inherits. Riding the major means one dependency migration instead of two. - **Until 3.0 ships, each consumer covers itself** with the one-line override `"@nuxtjs/mdc": "$@nuxtjs/mdc"` (plus a direct `^0.22.2` if it declares mdc itself). Default-branch status as of 2026-09-21: - landscape: done (nis/landscape `ba24548`). - yard, tti-ai-studio: only on unmerged feature branches. - atlas, tti-ai-studio-sampler: not yet. The sampler renders LLM output through MDC. So does helm2, which uses mdc directly rather than through tux. Those two are the real exposures, and neither is fixed by this PR. ## When landing with 3.0 - Move `packages/react`'s peer to `^3.0.0` in the same release. Once 3.0.0 is published and the lockfile refreshed, the peer copy carries `^0.22.2` itself, and the override and its `"//"` note can go. - Fold this `## [Unreleased]` entry into the 3.0 section when cutting the release. - Re-run trivy. If the four transitive findings above are handled by then, the gate goes green. - The GitHub mirror's `main` carries three brand-logo commits (`561c641..03923db`) that aren't on Forgejo `main`; reconcile them before the release push, since `deploy-pages.yml` builds from GitHub `main`.
fix(security): @nuxtjs/mdc ^0.22.2 (CVE-2026-63671)
Some checks failed
scan / trivy-fs (push) Failing after 1m37s
baseline-security / baseline (push) Failing after 1m56s
ai-review / review (pull_request) Successful in 1m42s
baseline-security / baseline (pull_request) Failing after 2m11s
scan / trivy-fs (pull_request) Failing after 1m17s
53206271a7
mdc below 0.22.1 lets `javascript:` on an SVG <a xlink:href> and a
`data:text/html` src through its markdown sanitizer (GHSA-mxm6-v9r6-r94c,
HIGH). Every tux surface that renders through MDC sits behind it, and so
does every consumer that extends the layer.

- dependencies: ^0.21.1 -> ^0.22.2. Not ^0.22.0 (still vulnerable); 0.22.2
  also validates bound (:href) and @event attribute names.
- overrides: "@nuxtjs/mdc": "$@nuxtjs/mdc". packages/react's peer on
  @tti/tti-ux ^2.0.0 installs a published 2.0.0 that still declares
  ^0.21.1; without the override npm nests a vulnerable 0.21.1 under it.
  It only shapes this repo's install; consumers never see it.
- Lockfile regenerated with npm 12.0.2. npm 11.12.1 does not propagate
  the override through the workspace peer and keeps the nested 0.21.1;
  the npm 12 result is the npm 11 result minus that one entry. npm ci
  accepts it under 11.12.1, 11.19.0 and 12.0.2 (npm 10 already fails on
  main: Missing cac/commander).

Verified: all 112 tracked .md files parse to identical ASTs under 0.21.1
and 0.22.2; the Pages build's 36 MDC-rendered pages match main except
markdown/index.html, whose only difference is inlined icon CSS; trivy
HIGH/CRITICAL 5 -> 4, clearing only this CVE; typecheck, lint and the
react workspace tests pass; the root vitest result matches main exactly.
ai-review-bot left a comment

AI review · advisory

Verdict: looks good — all four reviewers found nothing that needs fixing.

⚑ panel: GPT-OSS 120B · Gemma 4 31B · Devstral 2 123B · Laguna S 2.1 — 0 distinct, 0 confirmed, 0 below threshold, 0 refuted · web: not used · context: 3 files under review · no reference chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)

Panel debate — how this review was reached

Grounding — context: 3 files under review · no reference chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)

Round 1 — independent reviews

  • GPT-OSS 120B (0 findings):
  • Gemma 4 31B (0 findings):
  • Devstral 2 123B (0 findings):
  • Laguna S 2.1 (0 findings):

Synthesis — Laguna S 2.1 wrote the final review from 0 confirmed findings · promotion: support ≥ 2, and no refutation at high severity.

Transcript rv-20260922005013-955300 — full round outputs, web results, and model reasoning are viewable by anyone with access to this repository via the AI gateway.

Advisory — never a merge gate. Findings are ordered by how well the panel's own evidence checks out, strongest first. React 👍/👎 on any inline comment to tell it whether it was worth flagging — that is the only feedback this system gets, and every threshold in it is tuned from those reactions. Transcript rv-20260922005013-955300.

### AI review · advisory <!-- tti-rv:rv-20260922005013-955300: --> **Verdict: looks good** — all four reviewers found nothing that needs fixing. <sub>⚑ panel: GPT-OSS 120B · Gemma 4 31B · Devstral 2 123B · Laguna S 2.1 — 0 distinct, 0 confirmed, 0 below threshold, 0 refuted · web: not used · context: 3 files under review · no reference chunks (best-grounded: GPT-OSS 120B; smaller windows saw less)</sub> <details> <summary>Panel debate — how this review was reached</summary> **Grounding** — context: 3 files under review · no reference chunks (best-grounded: GPT-OSS 120B; smaller windows saw less) **Round 1 — independent reviews** - **GPT-OSS 120B** (0 findings): - **Gemma 4 31B** (0 findings): - **Devstral 2 123B** (0 findings): - **Laguna S 2.1** (0 findings): **Synthesis** — Laguna S 2.1 wrote the final review from 0 confirmed findings · promotion: support ≥ 2, and no refutation at high severity. <sub>Transcript `rv-20260922005013-955300` — full round outputs, web results, and model reasoning are viewable by anyone with access to this repository via the AI gateway.</sub> </details> <sub>Advisory — never a merge gate. Findings are ordered by how well the panel's own evidence checks out, strongest first. React 👍/👎 on any inline comment to tell it whether it was worth flagging — that is the only feedback this system gets, and every threshold in it is tuned from those reactions. Transcript `rv-20260922005013-955300`.</sub>

🔧 Security-gate fix map

The gate failed on these dependency findings — fastest path to green for each:

finding package installed → fixed do this
GHSA-j95f-988m-3j2f (HIGH) @tiptap/core 3.28.0 → 3.30.5 no fix PR yet — npm update core --package-lock-only
CVE-2026-84375 (HIGH) js-yaml 4.3.1 → 4.3.2, 3.15.2 no fix PR yet — npm update js-yaml --package-lock-only
GHSA-rgj7-g3m4-5g8c (HIGH) sharp 0.35.3 → 0.35.4 no fix PR yet — npm update sharp --package-lock-only
CVE-2026-84370 (HIGH) svgo 4.0.2 → 2.8.4, 3.3.5, 4.1.0 no fix PR yet — npm update svgo --package-lock-only

⚠ main is itself red right now — this PR likely inherits the backlog rather than adding it. Fixing main (rows above) unblocks every open PR at once.

Posted once per head commit by the baseline gate (M2). A Renovate PR that only touches a manifest with no lockfile change is a broken pre-2026-08-06 artifact — check its diff before merging.

### 🔧 Security-gate fix map <!-- tti-fixmap:53206271a76f5fb9ca770e71a303e21b4e2e12d4 --> The gate failed on these dependency findings — fastest path to green for each: | finding | package | installed → fixed | do this | |---|---|---|---| | GHSA-j95f-988m-3j2f (HIGH) | `@tiptap/core` | 3.28.0 → 3.30.5 | no fix PR yet — `npm update core --package-lock-only` | | CVE-2026-84375 (HIGH) | `js-yaml` | 4.3.1 → 4.3.2, 3.15.2 | no fix PR yet — `npm update js-yaml --package-lock-only` | | GHSA-rgj7-g3m4-5g8c (HIGH) | `sharp` | 0.35.3 → 0.35.4 | no fix PR yet — `npm update sharp --package-lock-only` | | CVE-2026-84370 (HIGH) | `svgo` | 4.0.2 → 2.8.4, 3.3.5, 4.1.0 | no fix PR yet — `npm update svgo --package-lock-only` | > ⚠ `main` is itself red right now — this PR likely **inherits** the backlog rather than adding it. Fixing `main` (rows above) unblocks every open PR at once. <sub>Posted once per head commit by the baseline gate (M2). A Renovate PR that only touches a manifest with no lockfile change is a broken pre-2026-08-06 artifact — check its diff before merging.</sub>
Some checks failed
scan / trivy-fs (push) Failing after 1m37s
baseline-security / baseline (push) Failing after 1m56s
ai-review / review (pull_request) Successful in 1m42s
baseline-security / baseline (pull_request) Failing after 2m11s
Required
Details
scan / trivy-fs (pull_request) Failing after 1m17s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/mdc-cve-2026-63671:fix/mdc-cve-2026-63671
git switch fix/mdc-cve-2026-63671
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
You do not have permission to read 1 dependency
Reference
tti/tti-ux!59
No description provided.