Skip to content

Commit cdb9646

Browse files
committed
fix(crosswalk): render resolved control ids, never the URL text
CodeQL still reported js/xss after the first pass. The SARIF flow showed the FRAMEWORKS allow-list did cut the framework-name path, but two things were not barriers it recognises: * isPlausibleControlId used a negated character class (!/[<>]/.test), and CodeQL models positive anchored matches, not negations * toNode returned its argument unchanged on the node branch, so taint flowed straight back out to appendChild Replace the character check with resolution. resolveControlId looks the id up in the same three places renderControlDetail reads - the framework registry, the backlink index, then DATA mappings - and returns the stored string. The route renders the resolved value and the allow-listed framework name, so no URL-derived string reaches the DOM at all. An id that resolves to nothing falls through to the frameworks index, which is all an unmatched id could have rendered anyway. Verified every real deep link still resolves to itself: 1097/1097 backlink, 1514 registry, 3210 DATA mapping links, 0 broken. Payloads and __proto__/constructor keys all resolve to null.
1 parent b9ad99d commit cdb9646

1 file changed

Lines changed: 35 additions & 16 deletions

File tree

‎crosswalk/docs/index.html‎

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,14 +1642,28 @@
16421642
'/review': renderReview
16431643
};
16441644

1645-
// Control ids are drawn from 25 separate framework registries and are
1646-
// punctuation-heavy ("GV-1.7", "Art. 24-27", "A.5.1"), so this bounds the
1647-
// length and rejects markup delimiters rather than allow-listing a shape
1648-
// that would reject legitimate ids.
1649-
function isPlausibleControlId(value) {
1650-
return typeof value === 'string' &&
1651-
value.length > 0 && value.length <= 200 &&
1652-
!/[<>]/.test(value);
1645+
// Resolve a control id supplied in the URL to the canonical string held in
1646+
// our own data, searching the same three places renderControlDetail reads:
1647+
// the framework registry, the backlink index, then the mappings in DATA.
1648+
// Returns null when nothing matches. Callers render the returned value
1649+
// rather than the URL, so nothing user-supplied is ever put on the page —
1650+
// a control id that matches nothing could only have produced an empty page
1651+
// echoing the URL back anyway.
1652+
function resolveControlId(fwName, controlId) {
1653+
var regFw = FW_REGISTRY_MAP[fwName];
1654+
if (regFw) {
1655+
var regControl = (regFw.controls || []).find(function(c) { return c.control_id === controlId; });
1656+
if (regControl) return regControl.control_id;
1657+
}
1658+
var bl = BACKLINK_MAP[fwName + '::' + controlId];
1659+
if (bl) return bl.control_id;
1660+
var found = null;
1661+
DATA.forEach(function(e) {
1662+
(e.mappings || []).forEach(function(m) {
1663+
if (found === null && m.framework === fwName && m.control_id === controlId) found = m.control_id;
1664+
});
1665+
});
1666+
return found;
16531667
}
16541668

16551669
function getRoute() {
@@ -1687,14 +1701,19 @@
16871701
if (fwControlMatch) {
16881702
var fwNameCtrl = decodeURIComponent(fwControlMatch[1]);
16891703
var ctrlId = decodeURIComponent(fwControlMatch[2]);
1690-
// Apply the same allow-list the plain framework route uses below. An
1691-
// unknown framework name has no registry entry and no mappings, so it
1692-
// could only ever render an empty page echoing the URL back; fall
1693-
// through to the frameworks index instead.
1694-
if (FRAMEWORKS.indexOf(fwNameCtrl) !== -1 && isPlausibleControlId(ctrlId)) {
1695-
renderControlDetail(app, fwNameCtrl, ctrlId);
1696-
window.scrollTo(0, 0);
1697-
return;
1704+
// Resolve both halves of the deep link against our own data and render
1705+
// the resolved values, never the URL text. The framework name gets the
1706+
// same allow-list the plain framework route uses below; an unknown
1707+
// framework or control falls through to the frameworks index.
1708+
var fwIdx = FRAMEWORKS.indexOf(fwNameCtrl);
1709+
if (fwIdx !== -1) {
1710+
var knownFw = FRAMEWORKS[fwIdx];
1711+
var knownCtrl = resolveControlId(knownFw, ctrlId);
1712+
if (knownCtrl !== null) {
1713+
renderControlDetail(app, knownFw, knownCtrl);
1714+
window.scrollTo(0, 0);
1715+
return;
1716+
}
16981717
}
16991718
}
17001719
// Framework detail is a full page, not a modal

0 commit comments

Comments
 (0)