fix(security): escape MDX syntax in reference-architecture submissions - #812
Merged
Merged
Conversation
Pages under docs/ compile as MDX: @docusaurus/core 3 defaults
markdown.format to `mdx` and docusaurus.config.js sets no override. The
reference-architecture issue importer wrote submitters' free-text answers
into docs/architectures/<id>.md verbatim, so `{...}` reached the compiler
as a JavaScript expression, `<Tag onClick={...}>` as a live React element,
and a block-initial `import`/`export` as a top-level ESM statement.
All three executed in Node during the `npm run build` step of
architecture-submission.yml, before create-pull-request committed the
working tree, and the JSX props would have fired in visitors' browsers
once the generated page merged.
Escape the answers in renderSection() rather than in the shared
cleanMarkdown(): the sections are joined with the generated
<CNCFProjectCard /> markup before that helper runs, so escaping the joined
text would destroy the cards.
MDX evaluates nothing inside a fenced code block or an inline code span, so
the new escaper leaves those verbatim and an architecture description can
still show a YAML or JSON snippet. Every substitution is a character
reference that renders as the original character, so escaped prose displays
as written; the catalog summary is unescaped again because it is stored as
JSON and rendered as a plain text node.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: hivecommons-hive[bot] <hivecommons-hive@hive.kubestellar.io>
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 Hive will automatically remove the |
This was referenced Sep 28, 2026
Contributor
Author
CI note (ci-maintainer): the red
|
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security Fix
Closes #808.
Pages under
docs/compile as MDX: this repo pins@docusaurus/core3.10.2 and sets no
markdown.formatindocusaurus.config.js, so theDocusaurus 3 default (
mdx) applies.scripts/import-architecture-issue.mjswrote a submitter's free-text answers into
docs/architectures/<id>.mdverbatim, so everything a submitter typed reached a JavaScript compiler.
.github/workflows/architecture-submission.ymlruns that importer and thennpm run buildon the result, in a job holdingcontents: write,pull-requests: writeandissues: write, beforepeter-evans/create-pull-requestcommits the working tree.What changes
New
scripts/lib/mdx-escape.mjsexportingescapeMdx()/unescapeMdx(),applied to the submitted answers in
renderSection().The escaping is deliberately not in the shared
cleanMarkdown():renderProjectsSection()emits real<CNCFProjectCard />markup and thesections are joined before
cleanMarkdown()runs, so escaping there woulddestroy the cards. Nothing on the
cncf/architectureimporter's path changes.Outside fenced code blocks and inline code spans,
escapeMdx():{/}with{/},<with<, preserving<https://…>and<user@host>autolinks,import/export(
import,export), which compiles with no top-level ESMstatement while still displaying as
import/export.Every substitution is a character reference that renders as the character the
author typed, so escaped prose displays as written. MDX evaluates nothing
inside code, so fences and code spans are passed through untouched and an
architecture description can still show a YAML or JSON snippet with braces
intact. The catalog summary is run back through
unescapeMdx()because it isstored as JSON and rendered as a plain text node, where a character reference
would show through literally.
jsxElement()and the artwork mirror were already safe and are untouched.Verification
Before, the importer's output built to
<p>Normal-looking prose. <!-- -->4</p>—
2 + 2evaluated in Node inside the build. After, the same submissionbuilds to
<p>Normal-looking prose. {2 + 2}</p>and<p><span onClick={() => alert(1)}>click</span></p>: the author'scharacters, displayed, with nothing for the compiler to execute.
npm run check— exit 0npm run test:unit— 1452 tests, 1450 pass, 0 failnpm run test:unit:coverage:check— exit 0;scripts/lib/mdx-escape.mjsat 100.00% lines / 98.72% regions
npm run build:production— exit 030 new tests (24 unit in
tests/mdx-escape.test.mjs, 6 integration throughthe existing importer sandbox). Mutation-checked both directions: dropping
the
escapeMdx()call fails the 3 injection tests, and dropping thefenced-code carve-out fails the 2 verbatim-code tests.
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