Merge pull request #29381 from backstage/renovate/npm-dompurify-vulnerability

fix(deps): update dependency dompurify to v3.2.4 [security]
This commit is contained in:
Fredrik Adelöw
2025-09-10 16:28:54 +02:00
committed by GitHub
10 changed files with 133 additions and 36 deletions
+1 -1
View File
@@ -81,7 +81,7 @@
"@material-ui/lab": "4.0.0-alpha.61",
"@material-ui/styles": "^4.10.0",
"@microsoft/fetch-event-source": "^2.0.1",
"dompurify": "^3.0.0",
"dompurify": "^3.2.4",
"git-url-parse": "^15.0.0",
"jss": "~10.10.0",
"lodash": "^4.17.21",
@@ -0,0 +1,32 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UponSanitizeAttributeHook } from 'dompurify';
/**
* Removes attributes that should only be present on meta tags from other elements.
* This ensures that http-equiv and content attributes are only allowed on meta tags
* where they are required for the redirect feature.
*/
export const removeRestrictedAttributes: UponSanitizeAttributeHook = (
node,
data,
) => {
if (node.tagName !== 'META') {
if (data.attrName === 'http-equiv' || data.attrName === 'content') {
node.removeAttribute(data.attrName);
}
}
};
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { isElement } from '../utils';
/**
* Checks whether a node is iframe or not.
* @param node - can be any element.
@@ -42,9 +44,10 @@ const isSafe = (node: Element, hosts: string[]) => {
* @param node - can be any element.
* @param hosts - list of allowed hosts.
*/
export const removeUnsafeIframes = (hosts: string[]) => (node: Element) => {
export const removeUnsafeIframes = (hosts: string[]) => (node: Node) => {
if (!isElement(node)) return;
if (isIframe(node) && !isSafe(node, hosts)) {
node.remove();
}
return node;
};
@@ -16,3 +16,5 @@
export { removeUnsafeLinks } from './links';
export { removeUnsafeIframes } from './iframes';
export { removeUnsafeMetaTags } from './metatags';
export { removeRestrictedAttributes } from './attributes';
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { isElement } from '../utils';
const MKDOCS_CSS = /main\.[A-Fa-f0-9]{8}\.min\.css$/;
const GOOGLE_FONTS = /^https:\/\/fonts\.googleapis\.com/;
const GSTATIC_FONTS = /^https:\/\/fonts\.gstatic\.com/;
@@ -41,11 +43,11 @@ const isSafe = (node: Element) => {
/**
* Function that removes unsafe link nodes.
* @param node - can be any element.
* @param hosts - list of allowed hosts.
*/
export const removeUnsafeLinks = (node: Element) => {
export const removeUnsafeLinks = (node: Node) => {
if (!isElement(node)) return;
if (isLink(node) && !isSafe(node)) {
node.remove();
}
return node;
};
@@ -0,0 +1,41 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UponSanitizeElementHook } from 'dompurify';
import { isElement } from '../utils';
/**
* Checks if a meta tag is a refresh redirect tag that should be allowed.
* These tags are required for the TechDocs redirect feature.
*/
const isAllowedMetaRefreshTag = (element: Element): boolean => {
const httpEquiv = element.getAttribute('http-equiv');
const content = element.getAttribute('content');
return httpEquiv === 'refresh' && content?.includes('url=') === true;
};
/**
* Removes unsafe meta tags from the DOM while preserving allowed refresh redirect tags.
* Only meta tags used for page refreshing/redirects are allowed as they are required
* for the TechDocs redirect feature.
*/
export const removeUnsafeMetaTags: UponSanitizeElementHook = (node, data) => {
if (!isElement(node)) return;
if (data.tagName === 'meta' && !isAllowedMetaRefreshTag(node)) {
node.parentNode?.removeChild(node);
}
};
@@ -20,7 +20,12 @@ import { useCallback, useMemo } from 'react';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { Transformer } from '../transformer';
import { removeUnsafeIframes, removeUnsafeLinks } from './hooks';
import {
removeRestrictedAttributes,
removeUnsafeIframes,
removeUnsafeLinks,
removeUnsafeMetaTags,
} from './hooks';
/**
* Returns html sanitizer configuration
@@ -51,26 +56,9 @@ export const useSanitizerTransformer = (): Transformer => {
DOMPurify.addHook('beforeSanitizeElements', removeUnsafeIframes(hosts));
}
// Only allow meta tags if they are used for refreshing the page. They are required for the redirect feature.
DOMPurify.addHook('uponSanitizeElement', (currNode, data) => {
if (data.tagName === 'meta') {
const isMetaRefreshTag =
currNode.getAttribute('http-equiv') === 'refresh' &&
currNode.getAttribute('content')?.includes('url=');
if (!isMetaRefreshTag) {
currNode.parentNode?.removeChild(currNode);
}
}
});
DOMPurify.addHook('uponSanitizeElement', removeUnsafeMetaTags);
// Only allow http-equiv and content attributes on meta tags. They are required for the redirect feature.
DOMPurify.addHook('uponSanitizeAttribute', (currNode, data) => {
if (currNode.tagName !== 'META') {
if (data.attrName === 'http-equiv' || data.attrName === 'content') {
currNode.removeAttribute(data.attrName);
}
}
});
DOMPurify.addHook('uponSanitizeAttribute', removeRestrictedAttributes);
const tagNameCheck = config?.getOptionalString(
'allowedCustomElementTagNameRegExp',
@@ -122,7 +110,7 @@ export const useSanitizerTransformer = (): Transformer => {
? new RegExp(attributeNameCheck)
: undefined,
},
});
}) as Element;
},
[config],
);
@@ -0,0 +1,19 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const isElement = (node: Node): node is Element => {
return node.nodeType === Node.ELEMENT_NODE;
};