[sec-check] MDX active-content gate resolves duplicate link definitions last-wins, bypassing the remote-image check
I maintainer di solito rispondono entro 1 giorno
Una pull request collegata è già stata integrata.
- #1673 di @hivecommons-hive — integrata
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 25/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Ferma
- Stack tecnologico
- javascript
- Ambito
- security
Direzione di ricerca
Read scripts/lib/mdx-active-content.mjs and scripts/lib/architecture-content.mjs, then inspect the duplicate-definition handling described in the issue. Run the named tests in tests/mdx-active-content.test.mjs and tests/architecture-content-clean-markdown.test.mjs; done means both maps use the first definition and the tests cover remote/local ordering and cleanMarkdown() rewriting.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Security Finding
Severity: medium
Type: unsafe-pattern (security-control bypass)
scripts/lib/mdx-active-content.mjs and scripts/lib/architecture-content.mjs each build their link-reference definition map with a bare Map.set(), so a repeated label resolves to the last definition in the document:
scripts/lib/mdx-active-content.mjs:159—if (node.type === 'definition') definitions.set(node.identifier, node.url);scripts/lib/architecture-content.mjs:81—if (node.type === 'definition') definitions.set(node.identifier, node.url);
CommonMark resolves a repeated label to the first definition, and the renderer this site actually publishes through does the same. node_modules/mdast-util-to-hast/lib/state.js:227-237 builds its map under if (!map.has(id)) with the comment "Mimick CM behavior of link definitions.", and @mdx-js/mdx runs that transform. So for a document carrying two definitions of one label, the gate inspects a URL the published page never uses.
Reproduction (run at a621619, Node 26, repo node_modules installed)
import { findActiveContent } from './scripts/lib/mdx-active-content.mjs';
import { compile } from '@mdx-js/mdx';
const body = `# Imported architecture
![diagram][d]
[d]: https://beacon.evil.example/px.png
[d]: /img/architectures/local.png
`;
console.log('findActiveContent =>', JSON.stringify(findActiveContent(body)));
console.log(String(await compile(body)).includes('beacon.evil.example'));
Output:
findActiveContent => []
true <-- compiled page contains src: "https://beacon.evil.example/px.png"
The gate reports zero findings while the compiled page loads the third-party image.
Impact
Bounded, and deliberately stated narrowly:
- Not XSS. Every
definitionnode is checked for a script-capable scheme regardless of which one wins, becauseURL_NODESinmdx-active-content.mjs:100includesdefinitionand that arm runs per node rather than through the map. Ajavascript:destination is still caught on both copies. - The remote-image beacon check is bypassed.
reportRemoteImage()is the only check that resolves a reference through the map, so a remote<img>destination reaches the published page unflagged. That is precisely the control the module's own header documents: "this is the gate on what is published, and it has to hold when a page reachesdocs/without passing the importer." A remote image is fetched by every visitor's browser with no user action, disclosing their IP, User-Agent and Referer to a host the page author chose. - Reachable at the gate that matters.
scripts/validate-architectures.mjs:135runsfindActiveContent()over every.mdon disk underdocs/architectures/, driven by the filesystem rather than the catalog, so this is the check standing between a page added by a pull request and publication. architecture-content.mjsis a correctness bug, not an exposure.rewriteImages()replaces the wholeimageReferencenode with an inline image, so whichever definition it picks the emitted destination is mirrored, rebased or demoted — the output stays safe. But it applies the image policy to a URL the renderer would not have used, which means the published page can show the wrong diagram, and it leaves the two modules disagreeing about reference resolution when they are written to agree.
Recommendation
Make both maps first-wins, matching CommonMark and mdast-util-to-hast:
scripts/lib/mdx-active-content.mjs
eachNode(tree, (node) => {
// First definition wins, as CommonMark specifies and as
// mdast-util-to-hast implements for the published page; a later
// duplicate of the same label is dead text the renderer ignores.
if (node.type === 'definition' && !definitions.has(node.identifier))
definitions.set(node.identifier, node.url);
});
scripts/lib/architecture-content.mjs
if (node.type === 'definition' && !definitions.has(node.identifier))
definitions.set(node.identifier, node.url);
Both identifier keys stay consistent with the renderer: mdast normalizes identifier, and mdast-util-to-hast upper-cases that normalized value, which is injective, so the two collapse the same labels.
Add regression fixtures so a future refactor cannot reintroduce last-wins:
tests/mdx-active-content.test.mjs— a duplicate-label document whose first definition is remote and second is local must reportremote image destination …, and the mirror case (first local, second remote) must report nothing.tests/architecture-content-clean-markdown.test.mjs—cleanMarkdown()must rewrite a duplicated reference from its first definition.
Completion criteria
-
mdx-active-content.mjsresolves a repeated label to the first definition -
architecture-content.mjsresolves a repeated label to the first definition - Regression tests cover both orderings in
tests/mdx-active-content.test.mjs - Regression test covers
cleanMarkdown()duplicate-label rewriting
No workflow file is involved; the whole fix is pushable.
Filed by sec-check agent (ACMM L4/L5 — hold-gated mode)
🐝 Hive Agent: security | Instance: hosted-available-lke648397-260827-5n31 | SHA: a621619
— hive: agent=sec-check backend=copilot model=claude-opus-5 copilot=1.0.88
- Lingua principale
- JavaScript
- Stelle
- 0
- Fork
- 3
- Merge medio
- 7h 21m
- PR unite (30g)
- 575
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di cncf/endusers
-
agent/ci-maintainer ci good first issue help wanted stage/010-actionable
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100
cncf/endusers#1890 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[guide] Public Sector User Group page names wrong Slack channelForse già presa @hivecommons-hive l’ha presa 1 giorno fa. Apertaagent/guide documentation hive/claimed-by-guide stage/040-covered-by-pr
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
cncf/endusers#1874 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[guide] Governance page renders an empty bullet — stray `- ` line in the reproduced ug-governance docForse già presa @hivecommons-hive l’ha presa oggi. Apertaagent/guide documentation hive/claimed-by-guide stage/040-covered-by-pr
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 25/100
cncf/endusers#1892 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[quality] ProjectsBorn tiles show the external-link arrow but navigate in the same tabForse già presa @hivecommons-hive l’ha presa oggi. Apertaagent/quality hive/claimed-by-scanner stage/040-covered-by-pr testing
Difficoltà 2/5 1-3 ore Idoneità per principianti 30/100
cncf/endusers#1891 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[scanner] Homepage lists five End User Group verticals; two are archived upstream and three have no page in the site catalogForse già presa @hivecommons-hive l’ha presa oggi. Apertaagent/scanner documentation stage/040-covered-by-pr
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 30/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di cncf/endusers
Issue simili
-
Plaid sync fails and rolls back when a removed pending transaction was matched by a bank ruleAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
bigcapitalhq/bigcapital#1544 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
help wanted kind/bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
meshery/meshery.io#3112 ·
I maintainer di solito rispondono entro 2 giorni
-
area:docs type:chore
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
bulkhead-labs/tailsuite#31 ·
-
dependencies javascript
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
gems-uff/noworkflow#470 ·
I maintainer di solito rispondono entro 3 giorni
-
product / auth product / console product / sites product / storage
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno