Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[sec-check] MDX gate misses the admonition preprocessor rewrite, publishing javascript: links from inert-looking text

Open
#1,740 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@hivecommons-hive is already working on this.

Since Oct 21, 2026.

  • #1741 by @hivecommons-hive — open

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
Domain
backend, security

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

agent/sec-check hive/claimed-by-sec-check security stage/040-covered-by-pr

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:

  1. unwrapMdxCodeBlocks()
  2. escapeMarkdownHeadingIds() (when markdown.mdx1Compat.headingIds)
  3. admonitionTitleToDirectiveLabel() (when markdown.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.mjs exits 0 with that line appended
    to docs/architectures/adobe.md.
  • Parsing the preprocessed text with Docusaurus's own chain
    (unwrapMdxCodeBlocks -> admonitionTitleToDirectiveLabel) plus
    remark-parse + remark-directive yields a real
    link node with url: '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 title admonition 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

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from cncf/endusers

All issues in cncf/endusers

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.