Skip to content

fix(mdx-active-content): match the element allowlist case-sensitively and reject string style attributes - #1334

Merged
mrbobbytables merged 2 commits into
mainfrom
sec/mdx-active-content-render-parity
Oct 12, 2026
Merged

mrbobbytables merged 2 commits into
mainfrom
sec/mdx-active-content-render-parity

Conversation

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Security Fix

findActiveContent() states its own invariant up front: text the parser rejects
is a finding, because a scan that cannot read a document cannot vouch for it.
Two inputs broke that invariant in the other direction — the scan returned no
findings, validate:architectures passed, and Docusaurus threw while
prerendering the page.

Element allowlist matched case-insensitively

scripts/lib/mdx-active-content.mjs resolved the tag name through
ALLOWED_ELEMENTS.has(name.toLowerCase()). MDX reads a lower-case tag name as an
HTML element and any other casing as a component reference, so <B> is not
the inert <b> the allowlist permits — it compiles to a _missingMdxReference
lookup that throws Expected component B to be defined. Verified at fb75c1a:
findActiveContent() returned [] for <B>bold</B>, <Code>x</Code> and
<HR />.

Every entry in ALLOWED_ELEMENTS is already lower case, so matching
case-sensitively removes only the component-reference casings.

String-valued style attribute unreported

React requires style to be an object; renderToStaticMarkup on a string
style prop throws "The style prop expects a mapping from style properties to
values, not a string"
. style={"..."} reached React as a string too, because
literalValue() deliberately reads a lone string literal as inert. The object
form style={{ ... }} was already reported as an MDX expression, so with this
change no style attribute reaches a rendered page by any route.

Scope

Files and functions claimed by this PR:

  • scripts/lib/mdx-active-content.mjs — findActiveContent() element-name match
    and attribute loop; new STYLE_ATTRIBUTE constant
  • tests/mdx-active-content.test.mjs — two added regression tests
  • CONTENT-SECURITY.md — "Markdown/MDX page rules" section

Disjoint from the open hold-gated PR #1330, which covers DirectoryFreshness
end-to-end coverage.

Verification

  • tests/mdx-active-content.test.mjs: 41 pass, 0 fail (39 pre-existing + 2 added)
  • tests/content-security-contract.test.mjs: 13 pass, 0 fail — the documented
    element list is unchanged, so the exact-match contract still holds
  • npm run validate:architectures — "Validated 8 architecture records"
  • npm run validate:architecture-assets — clean
  • npx prettier --check clean on all three files
  • No content in docs/ uses either construct, so the tightening is non-breaking

This is a gate-correctness and availability fix, not an exploitable-XSS fix. I
confirmed the remote-beacon angle is independently closed: style never reaches
a rendered page, and CSP img-src in docusaurus.config.js governs CSS
background images regardless.

Closes #1333


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

@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.

@hivecommons-hive

Copy link
Copy Markdown
Contributor Author

CI triage (ci-maintainer): Lint repository / Check spelling is red on head 3e1d274

CONTENT-SECURITY.md:136:19 - Unknown word (prerendering)
CONTENT-SECURITY.md:140:7  - Unknown word (prerendering)
CSpell: Files checked: 44, Issues found: 2 in 1 file.

The word prerendering is introduced by this PR's CONTENT-SECURITY.md changes — it does not appear on main, so this is PR-local, not a baseline failure. Fix: add prerendering to the words list in .cspell.yml (or reword those two sentences). Run npx cspell --no-progress -c .cspell.yml docs *.md locally to verify before pushing.

I cannot push to this lane's branch; leaving this as the finding record.


🐝 Hive Agent: ci-maintainer | Instance: hosted-available-lke648397-260827-5n31 | SHA: unknown

— hive: agent=ci-maintainer backend=copilot model=kimi-k3 copilot=1.0.88

sec-check and others added 2 commits October 12, 2026 08:54
… and reject string style attributes

findActiveContent() states that text the parser rejects is a finding,
because a scan that cannot read a document cannot vouch for it. Two
inputs broke that invariant in the other direction: the scan returned no
findings while Docusaurus threw during prerender.

The element allowlist was matched through name.toLowerCase(). MDX reads
a lower-case tag name as an HTML element and any other casing as a
component reference, so <B> is not the inert <b> the allowlist permits:
it compiles to a _missingMdxReference lookup that throws "Expected
component `B` to be defined". Every entry in ALLOWED_ELEMENTS is already
lower case, so matching case-sensitively only removes the
component-reference casings.

A string-valued style attribute was also unreported. React requires
style to be an object and throws when handed a string, and
style={"..."} reaches React as a string too because literalValue()
reads a lone string literal as inert. The object form is already
reported as an MDX expression, so no style attribute now reaches a
rendered page by any route.

No content in docs/ uses either construct, so the tightening is
non-breaking.

Closes #1333

Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: mrbobbytables <mrbobbytables@users.noreply.github.com>
@mrbobbytables
mrbobbytables force-pushed the sec/mdx-active-content-render-parity branch from 3e1d274 to 8afdb51 Compare October 12, 2026 13:54
@mrbobbytables
mrbobbytables added this pull request to the merge queue Oct 12, 2026
Merged via the queue into main with commit eb75a9b Oct 12, 2026
9 checks passed
@mrbobbytables
mrbobbytables deleted the sec/mdx-active-content-render-parity branch October 16, 2026 01:00
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] mdx-active-content vouches for imported bodies Docusaurus cannot render: case-variant tag names and string style attributes

1 participant