mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
fix(frontend): update vulnerable sanitizer dependencies
This commit is contained in:
parent
d130d00867
commit
6ba85a1856
4 changed files with 111 additions and 24 deletions
46
frontend-modern/package-lock.json
generated
46
frontend-modern/package-lock.json
generated
|
|
@ -10,7 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.10.10",
|
||||
"dompurify": "^3.4.12",
|
||||
"dompurify": "^3.4.13",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-solid": "^0.545.0",
|
||||
"marked": "^17.0.1",
|
||||
|
|
@ -1029,9 +1029,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -1110,9 +1110,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2810,16 +2810,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"version": "5.0.9",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
|
|
@ -3361,9 +3361,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.12",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
|
||||
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
|
|
@ -3684,9 +3684,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -4201,9 +4201,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -5225,9 +5225,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"version": "3.3.18",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.10.10",
|
||||
"dompurify": "^3.4.12",
|
||||
"dompurify": "^3.4.13",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-solid": "^0.545.0",
|
||||
"marked": "^17.0.1",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as utils from '@/components/AI/aiChatUtils';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { marked } from 'marked';
|
||||
import type { ModelInfo } from '@/types/ai';
|
||||
|
||||
|
|
@ -175,6 +176,71 @@ describe('aiChatUtils', () => {
|
|||
expect(output).toContain('target="_blank"');
|
||||
expect(output).toContain('rel="noopener noreferrer"');
|
||||
});
|
||||
|
||||
it('removes executable descendants from an advisory-shaped detached-subtree payload', () => {
|
||||
const output = utils.renderMarkdown(
|
||||
[
|
||||
'<footer>',
|
||||
' <img src="x" onload="alert(1)" onerror="alert(2)">',
|
||||
'</footer>',
|
||||
'<a href="https://example.com">safe link</a>',
|
||||
].join('\n'),
|
||||
);
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = output;
|
||||
|
||||
expect(template.content.querySelector('footer, img, [onload], [onerror], script')).toBeNull();
|
||||
const link = template.content.querySelector('a');
|
||||
expect(link?.getAttribute('href')).toBe('https://example.com');
|
||||
expect(link?.getAttribute('target')).toBe('_blank');
|
||||
expect(link?.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
});
|
||||
|
||||
it.each(['beforeSanitizeElements', 'uponSanitizeElement'] as const)(
|
||||
'neutralizes a hook-detached IN_PLACE subtree through the %s hook',
|
||||
(hookName) => {
|
||||
// Configure the same singleton hooks used by renderMarkdown before
|
||||
// exercising DOMPurify's patched IN_PLACE hook-detachment path.
|
||||
utils.renderMarkdown('```ts\nconst safe = true;\n```');
|
||||
|
||||
const root = document.createElement('div');
|
||||
root.innerHTML =
|
||||
'<section><img id="detached-tail" onerror="alert(1)"></section><div>safe</div>';
|
||||
const detachedTail = root.querySelector<HTMLImageElement>('#detached-tail');
|
||||
expect(detachedTail).not.toBeNull();
|
||||
|
||||
const removeSection = (node: Node) => {
|
||||
if (node.nodeName === 'SECTION') (node as Element).remove();
|
||||
};
|
||||
if (hookName === 'beforeSanitizeElements') {
|
||||
DOMPurify.addHook('beforeSanitizeElements', removeSection);
|
||||
} else {
|
||||
DOMPurify.addHook('uponSanitizeElement', removeSection);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = DOMPurify.sanitize(root, {
|
||||
ALLOWED_TAGS: ['div', 'section', '#text'],
|
||||
IN_PLACE: true,
|
||||
});
|
||||
|
||||
expect(result).toBe(root);
|
||||
expect(root.innerHTML).toBe('<div>safe</div>');
|
||||
expect(detachedTail?.isConnected).toBe(false);
|
||||
expect(detachedTail?.getAttribute('onerror')).toBeNull();
|
||||
|
||||
const probe = document.createElement('div');
|
||||
probe.innerHTML = `${root.outerHTML}${detachedTail?.outerHTML ?? ''}`;
|
||||
expect(probe.querySelector('[onerror], [onload], script')).toBeNull();
|
||||
} finally {
|
||||
if (hookName === 'beforeSanitizeElements') {
|
||||
DOMPurify.removeHook('beforeSanitizeElements', removeSection);
|
||||
} else {
|
||||
DOMPurify.removeHook('uponSanitizeElement', removeSection);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Mention ID format contracts', () => {
|
||||
|
|
|
|||
|
|
@ -108,6 +108,27 @@ describe('renderDocMarkdown', () => {
|
|||
expect(html).not.toContain('onerror');
|
||||
expect(html).not.toContain('javascript:');
|
||||
});
|
||||
|
||||
it('neutralizes an advisory-shaped nested resource payload before link rewriting', () => {
|
||||
const html = renderDocMarkdown(
|
||||
[
|
||||
'<footer>',
|
||||
' <img src="x" onload="alert(1)" onerror="alert(2)">',
|
||||
'</footer>',
|
||||
'<div><a href="INSTALL.md">safe link</a></div>',
|
||||
].join('\n'),
|
||||
'README',
|
||||
);
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = html;
|
||||
|
||||
expect(template.content.querySelector('footer, [onload], [onerror], script')).toBeNull();
|
||||
const image = template.content.querySelector('img');
|
||||
expect(image?.getAttribute('src')).toBe('x');
|
||||
const link = template.content.querySelector('a');
|
||||
expect(link?.getAttribute('href')).toBe('/docs/INSTALL');
|
||||
expect(link?.hasAttribute('data-doc-link')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapTables', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue