diff --git a/docs/index.html b/docs/index.html
index fee36a6..d0f12f6 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1537,6 +1537,14 @@
AI Security Standards Crosswalk
return div.innerHTML;
}
+ // Only a real DOM node may be appended. Anything else — most importantly a
+ // string that reached here from the URL — becomes a text node, so it is
+ // rendered as text and can never be parsed as markup.
+ function toNode(value) {
+ if (value && typeof value.nodeType === 'number') return value;
+ return document.createTextNode(String(value));
+ }
+
function el(tag, attrs, children) {
var node = document.createElement(tag);
if (attrs) {
@@ -1549,8 +1557,8 @@ AI Security Standards Crosswalk
}
if (children) {
if (typeof children === 'string') node.textContent = children;
- else if (Array.isArray(children)) children.forEach(function(c) { if (c) node.appendChild(c); });
- else node.appendChild(children);
+ else if (Array.isArray(children)) children.forEach(function(c) { if (c) node.appendChild(toNode(c)); });
+ else node.appendChild(toNode(children));
}
return node;
}
@@ -1674,6 +1682,31 @@ AI Security Standards Crosswalk
'/review': renderReview
};
+ // Resolve a control id supplied in the URL to the canonical string held in
+ // our own data, searching the same three places renderControlDetail reads:
+ // the framework registry, the backlink index, then the mappings in DATA.
+ // Returns null when nothing matches. Callers render the returned value
+ // rather than the URL, so nothing user-supplied is ever put on the page —
+ // a control id that matches nothing could only have produced an empty page
+ // echoing the URL back anyway.
+ function resolveControlId(fwName, controlId) {
+ var regFw = Object.prototype.hasOwnProperty.call(FW_REGISTRY_MAP, fwName) ? FW_REGISTRY_MAP[fwName] : null;
+ if (regFw) {
+ var regControl = (regFw.controls || []).find(function(c) { return c.control_id === controlId; });
+ if (regControl) return regControl.control_id;
+ }
+ var blKey = fwName + '::' + controlId;
+ var bl = Object.prototype.hasOwnProperty.call(BACKLINK_MAP, blKey) ? BACKLINK_MAP[blKey] : null;
+ if (bl) return bl.control_id;
+ var found = null;
+ DATA.forEach(function(e) {
+ (e.mappings || []).forEach(function(m) {
+ if (found === null && m.framework === fwName && m.control_id === controlId) found = m.control_id;
+ });
+ });
+ return found;
+ }
+
function getRoute() {
var hash = window.location.hash || '#/';
// Extract the path part (before any query params)
@@ -1709,9 +1742,20 @@ AI Security Standards Crosswalk
if (fwControlMatch) {
var fwNameCtrl = decodeURIComponent(fwControlMatch[1]);
var ctrlId = decodeURIComponent(fwControlMatch[2]);
- renderControlDetail(app, fwNameCtrl, ctrlId);
- window.scrollTo(0, 0);
- return;
+ // Resolve both halves of the deep link against our own data and render
+ // the resolved values, never the URL text. The framework name gets the
+ // same allow-list the plain framework route uses below; an unknown
+ // framework or control falls through to the frameworks index.
+ var fwIdx = FRAMEWORKS.indexOf(fwNameCtrl);
+ if (fwIdx !== -1) {
+ var knownFw = FRAMEWORKS[fwIdx];
+ var knownCtrl = resolveControlId(knownFw, ctrlId);
+ if (knownCtrl !== null) {
+ renderControlDetail(app, knownFw, knownCtrl);
+ window.scrollTo(0, 0);
+ return;
+ }
+ }
}
// Framework detail is a full page, not a modal
if (frameworkMatch) {