Skip to content

fix(security): stop the MDX code-span scan at a block boundary - #1122

Merged
mrbobbytables merged 1 commit into
mainfrom
sec/fix-mdx-code-span-block-boundary
Oct 7, 2026
Merged

mrbobbytables merged 1 commit into
mainfrom
sec/fix-mdx-code-span-block-boundary

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

findActiveContent() (scripts/lib/mdx-active-content.mjs) is the gate that keeps
imported third-party Markdown from shipping live MDX into the published site. Before
scanning, it blanks inline code spans; the closer search crossed newlines and stopped
only at a blank line.

CommonMark parses inlines one leaf block at a time, and a blank line is not the only
thing that ends a block. A list item, an ATX heading, a block quote, a thematic break
or an HTML block interrupts a paragraph with no blank line present, so an unclosed
backtick at the end of one block cannot pair with a backtick in the next one.
blankInlineSpans() paired them anyway and blanked everything between, and the
payload was never scanned:

- a `
- <script>alert(1)</script> `

returned [] on main, while both the reference commonmark parser and micromark
(the engine Docusaurus/MDX uses) render the element live.

What changed

Both changes are in scripts/lib/mdx-active-content.mjs:

  • New BLOCK_INTERRUPT_PATTERNS / startsNewBlock() — the line beginnings that
    start a new block: blank line, ATX heading, block quote, bullet and ordered list
    item, thematic break, and line-initial HTML block.
  • blankInlineSpans() stops the closer search at the first line that begins a new
    block instead of at a blank line.

Two deliberate choices, both documented in the source:

  • Code fences are absent from the list. blankFences() has already replaced every
    fence line with spaces by the time this runs, so a real fence is matched as a blank
    line. Adding a fence pattern would false-positive on ```<iframe>`, a line the
    real parser resolves into an ordinary code span (verified against micromark).
  • Ordered lists match any start number, and every line-initial < matches, though
    CommonMark only lets 1. and HTML block types 1-6 interrupt a paragraph. Stopping
    early can only leave more text scanned — the fail-closed direction this module
    states as its rule.

Verification

  • Differential against micromark: 66 documents (6 block prefixes x 11 following
    lines, payload <iframe src="https://evil.test">), comparing "rendered live by
    micromark" against "reported by findActiveContent" — 66/66 missed before this
    change, 0/66 after
    .
  • Two new tests: one covering the block-boundary bypass across a list item, heading,
    ordered item, block quote, line-initial HTML block and thematic break; one pinning
    that a genuine lazy paragraph continuation still forms a span and reports nothing.
  • node --test tests/mdx-active-content.test.mjs — 29/29 pass.
  • npm run test:unit:coverage:check — exits 0; scripts/lib/mdx-active-content.mjs
    and tests/mdx-active-content.test.mjs both at 100.00% lines / 100.00% regions.
  • npx prettier --check clean on both changed files.

Scope

Touches only scripts/lib/mdx-active-content.mjs and
tests/mdx-active-content.test.mjs; no workflow file is involved. Disjoint from every
open hold-gated PR: #1090 is scripts/lib/svg-active-content.mjs, #1112 is
package.json/package-lock.json, and #1104/#1110/#1113/#1114/#1117/#1120 are all
under tests/e2e*.

Closes #1121


Filed by sec-check agent (ACMM L4/L5 — hold-gated mode). Hold-gated: human review required.

— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88

blankInlineSpans() searched for a closing backtick run across newlines and
stopped only at a blank line. CommonMark parses inlines one leaf block at a
time, so an unclosed backtick at the end of one block cannot pair with a
backtick in the next; pairing them blanked every construct in between and the
payload reached the published site unscanned.

Closes #1121

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
@hivecommons-hive hivecommons-hive Bot added the hold label Oct 6, 2026
@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

Important

Held for human review by the hive's ACMM level gate.

This PR was opened by the "sec-check" agent while Hive policy required a human checkpoint for that agent. Non-outreach agents are held at ACMM L3–L5; the outreach agent is always held because it publishes project-facing communication.

Hive will keep the hold label until a human removes it. Operators can make a deliberate one-off release during an ACMM level change with release_level_holds=true, but level changes never release this hold automatically.

@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit d7e08a3 Oct 7, 2026
7 checks passed
@mrbobbytables
mrbobbytables deleted the sec/fix-mdx-code-span-block-boundary branch October 7, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[sec-check] findActiveContent misses live MDX hidden by a code span that crosses a block boundary

1 participant