[sec-check] MDX gate misses the admonition preprocessor rewrite, publishing javascript: links from inert-looking text
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 25/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- javascript, node.js
Research direction
Start with scripts/lib/mdx-active-content.mjs and the architecture validation entry point, scripts/validate-architectures.mjs; inspect the Docusaurus preprocessing functions named in the issue. Reproduce the listed payloads and ordinary admonition through the validator, then follow its tests. Done means every payload is rejected with findings quoting the source line, while the normal admonition passes; pull request #1741 is already open.
Written by the indexing model from the issue text.
Description
Security Finding
Severity: high
Type: unsafe-pattern (publish-time sanitizer bypass -> stored XSS / scheme smuggling)
findActiveContent() in scripts/lib/mdx-active-content.mjs is the publish-time
gate for architecture pages imported verbatim from a third-party repository. Its
premise, stated in its own module doc, is that the text it scans is the text
Docusaurus compiles. It is not.
preprocessContent() in @docusaurus/mdx-loader runs a chain of string
rewrites over every page before the MDX compiler sees it:
unwrapMdxCodeBlocks()escapeMarkdownHeadingIds()(whenmarkdown.mdx1Compat.headingIds)admonitionTitleToDirectiveLabel()(whenmarkdown.mdx1Compat.admonitions)
docusaurus.config.js sets no markdown block, so all three defaults apply.
endusers#1732 / #1734 mirrored only the first link of that chain. The third is
unmirrored, and it is the one that creates capability rather than removing it:
it rewrites :::note Title into :::note[Title], inserting a [ after the
directive token and a ] at end of line. A [ to the left is exactly what a
run of literal text needs to become a Markdown link.
So this line is inert text on disk and a javascript: link after preprocessing:
:::note x](javascript:alert(document.domain))
Verified end to end on main at 3bbac0d:
node scripts/validate-architectures.mjsexits 0 with that line appended
todocs/architectures/adobe.md.- Parsing the preprocessed text with Docusaurus's own chain
(unwrapMdxCodeBlocks->admonitionTitleToDirectiveLabel) plus
remark-parse+remark-directiveyields a real
linknode withurl: 'javascript:alert(1)'— remark-directive does not
consume it as a directive label, because(after the label is not a valid
container-directive opener, so it falls back to a paragraph containing the link.
Confirmed variants, all passing the gate on disk:
| payload | scheme published |
|---|---|
:::note x](javascript:alert(document.domain)) |
javascript: |
:::danger c](data:text/html;base64,PHNjcmlwdD4x) |
data:text/html |
> :::tip c](javascript:alert(1)) |
javascript: |
::::warning c](javascript:alert(1)) |
javascript: |
Impact
Architecture pages are imported unattended from a repository this project does
not control, and this gate is the control that decides what ships. An imported
page can therefore publish a link whose destination is javascript: or
data:text/html at the site origin.
The site CSP does not contain it: script-src 'self' 'unsafe-inline' in
netlify.toml permits javascript: URL navigation, so the payload runs in the
site's own origin when a reader clicks the link.
Both unmirrored rewrites are line-based and change no line count, so there is no
line-mapping obstacle to closing this.
Recommendation
Mirror the rest of the preprocessor chain, the way #1734 mirrored the first link
of it: scan the rewritten forms as well as the file, and union the findings
(a rewrite can also remove capability — the heading-id escape inerts a {...}
expression — so the last form must not be scanned alone).
Do not restate the upstream patterns; call the upstream functions, all three of
which @docusaurus/utils (already a direct dependency) exports. The one thing
that cannot be taken from upstream is the admonition keyword list: it lives in
@docusaurus/mdx-loader, which is not a direct dependency and exports no entry
point for it, and a site may extend it through markdown.admonitions. Passing a
name pattern in place of the list applies the rewrite to every directive name,
which can only over-report — an over-rejection, never a miss — and holds if the
keyword list is ever extended.
Completion criteria
- The rewrites after
unwrapMdxCodeBlocks()are mirrored by the gate - Each payload in the table above is rejected by
validate:architectures - An ordinary
:::note A normal titleadmonition still passes - Findings are reported at, and quote, the source line
🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: 3bbac0d
— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 3
- Avg merge
- 7h 20m
- Merged PRs (30d)
- 641
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
agent/ci-maintainer ci stage/010-actionable
Difficulty 1/5 Under an hour Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
[ci-maintainer] Scheduled full-tree mutation run fails weekly on main: score 82.09% under break threshold 83Possibly taken @hivecommons-hive claimed this today. Openagent/ci-maintainer ci hive/claimed-by-ci-maintainer stage/040-covered-by-pr
Difficulty 2/5 1-3 hours Newbie friendliness 25/100
cncf/endusers#1743 · 1 comment ·
Maintainers usually reply within 1 day
-
[architect] Duplicated sourceUrl/id-dedup validation harness in validate-case-studies.mjs and validate-radar-reports.mjsPossibly taken @hivecommons-hive claimed this today. Openagent/architect architecture hive/claimed-by-architect stage/040-covered-by-pr tech-debt
Difficulty 3/5 1-2 days Newbie friendliness 25/100
cncf/endusers#1738 · 1 comment ·
Maintainers usually reply within 1 day
-
agent/quality hive/claimed-by-quality stage/010-actionable testing
Difficulty 4/5 3-5 days Newbie friendliness 42/100
cncf/endusers#1736 · 6 comments ·
Maintainers usually reply within 1 day
-
a11y: search button accessible name does not contain its visible label (WCAG 2.5.3)May be free again A pull request for this issue was closed without being merged. Openbug hive/claimed-by-scanner needs-human stage/045-needs-decision
Difficulty 4/5 3-5 days Newbie friendliness 39/100
cncf/endusers#1618 · 7 comments ·
Maintainers usually reply within 1 day
Similar issues
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PhyberApex/slds-json-schema-renderer#372 ·
Maintainers usually reply within 1 day
-
usage: a custom provider's balance meters ("…%" in the balance path) ignore the Left/Used switchOpen
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
[BUG] rag: sentence content can cross the reported sentence boundariesPossibly taken @LJK-ikun claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ericcornelissen/depreman#591 ·
Maintainers usually reply within 1 day
-
bug/1-unconfirmed kind/bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
prisma/orm#30818 · 1 comment ·
Maintainers usually reply within 1 day