fix: deps & config (#15126)

This commit is contained in:
DarkSky
2026-06-18 14:41:48 +08:00
committed by GitHub
parent 24e07f73bb
commit 154d9e975d
8 changed files with 491 additions and 508 deletions
-1
View File
@@ -31,7 +31,6 @@
"groupSlug": "all-minor-patch",
"matchUpdateTypes": ["minor", "patch"],
"matchManagers": ["npm"],
"matchPackageNames": ["*"],
"excludePackagePatterns": ["^@blocksuite/", "^oxlint$"]
},
{
@@ -59,6 +59,12 @@ describe('sanitizeSvg', () => {
expect(sanitizeSvg('<div><svg></svg></div>')).toBe('');
});
test('rejects malformed doctype prefixes without regexp backtracking', () => {
const maliciousPrefix = '<!doctype' + '?><!doctype'.repeat(10_000);
expect(sanitizeSvg(`${maliciousPrefix}<div></div>`)).toBe('');
});
test('keeps internal glyph references and safe image data urls', () => {
const sanitized = sanitizeSvg(`
<svg xmlns="http://www.w3.org/2000/svg">
+51 -5
View File
@@ -26,8 +26,6 @@ const SAFE_IMAGE_DATA_URL_PATTERN =
/^data:image\/(?:png|jpe?g|gif|webp|svg\+xml);base64,[a-z0-9+/=]+$/i;
const UNSAFE_CSS_PATTERN =
/(?:url\s*\(|@import|javascript\s*:|expression\s*\(|-moz-binding)/i;
const SVG_ROOT_PATTERN =
/^\s*(?:<\?xml[\s\S]*?\?>\s*)?(?:<!doctype[\s\S]*?>\s*)?<svg[\s>]/i;
const SVG_ROOT_ATTRIBUTES = [
'class',
@@ -63,12 +61,60 @@ function getForeignObjectHtmlSanitizeConfig(options?: SanitizeSvgOptions) {
};
}
function isXmlWhitespace(char: string) {
return (
char === ' ' ||
char === '\n' ||
char === '\r' ||
char === '\t' ||
char === '\f'
);
}
function skipXmlWhitespace(value: string, index: number) {
while (index < value.length && isXmlWhitespace(value[index])) {
index++;
}
return index;
}
function startsWithIgnoreCase(value: string, search: string, index: number) {
return value.slice(index, index + search.length).toLowerCase() === search;
}
function getSvgRootStartIndex(value: string) {
let index = skipXmlWhitespace(value, 0);
if (startsWithIgnoreCase(value, '<?xml', index)) {
const declarationEnd = value.indexOf('?>', index + 5);
if (declarationEnd === -1) return -1;
index = skipXmlWhitespace(value, declarationEnd + 2);
}
if (startsWithIgnoreCase(value, '<!doctype', index)) {
const doctypeEnd = value.indexOf('>', index + 9);
if (doctypeEnd === -1) return -1;
index = skipXmlWhitespace(value, doctypeEnd + 1);
}
if (!startsWithIgnoreCase(value, '<svg', index)) return -1;
const next = value[index + 4];
return next === '>' || (next !== undefined && isXmlWhitespace(next))
? index
: -1;
}
function hasSvgRoot(value: string) {
return getSvgRootStartIndex(value) !== -1;
}
function getOriginalSvgRoot(svg: string, parser: DOMParser) {
const root = parser.parseFromString(svg, 'image/svg+xml').documentElement;
if (root?.tagName.toLowerCase() === 'svg') {
return root;
}
if (!SVG_ROOT_PATTERN.test(svg)) {
if (!hasSvgRoot(svg)) {
return null;
}
return parser.parseFromString(svg, 'text/html').querySelector('svg');
@@ -79,7 +125,7 @@ function ensureSvgRoot(
sanitized: string,
parser: DOMParser
) {
if (SVG_ROOT_PATTERN.test(sanitized)) {
if (hasSvgRoot(sanitized)) {
const sanitizedDoc = parser.parseFromString(sanitized, 'image/svg+xml');
const sanitizedRoot = sanitizedDoc.documentElement;
return sanitizedRoot?.tagName.toLowerCase() === 'svg'
@@ -228,7 +274,7 @@ function sanitizeSvgWithDepth(
) {
const sanitized = DOMPurify.sanitize(svg, svgConfig);
if (typeof sanitized !== 'string' || !SVG_ROOT_PATTERN.test(sanitized)) {
if (typeof sanitized !== 'string' || !hasSvgRoot(sanitized)) {
return '';
}
return sanitized.trim();
+16 -1
View File
@@ -167,7 +167,22 @@
"typedarray": "npm:@nolyfill/typedarray@^1",
"macos-alias": "npm:@napi-rs/macos-alias@0.0.4",
"fs-xattr": "npm:@napi-rs/xattr@latest",
"ioredis": "5.8.2",
"@opentelemetry/core": "^2.8.0",
"@opentelemetry/resources": "^2.8.0",
"@opentelemetry/sdk-trace-base": "^2.8.0",
"@tootallnate/once": "^2.0.1",
"ioredis": "^5.11.1",
"js-yaml@npm:^4.1.0": "^4.2.0",
"js-yaml@npm:4.1.1": "^4.2.0",
"multer": "^2.2.0",
"protobufjs": "^7.6.4",
"tar": "^7.5.16",
"tmp": "^0.2.7",
"ws@npm:^8.18.0": "^8.21.0",
"ws@npm:^8.18.3": "^8.21.0",
"ws@npm:^8.19.0": "^8.21.0",
"ws@npm:8.20.1": "^8.21.0",
"ws@npm:~8.17.1": "^8.21.0",
"decode-named-character-reference@npm:^1.0.0": "patch:decode-named-character-reference@npm%3A1.0.2#~/.yarn/patches/decode-named-character-reference-npm-1.0.2-db17a755fd.patch",
"@atlaskit/pragmatic-drag-and-drop": "patch:@atlaskit/pragmatic-drag-and-drop@npm%3A1.4.0#~/.yarn/patches/@atlaskit-pragmatic-drag-and-drop-npm-1.4.0-75c45f52d3.patch",
"yjs": "patch:yjs@npm%3A13.6.21#~/.yarn/patches/yjs-npm-13.6.21-c9f1f3397c.patch"
+18 -17
View File
@@ -45,27 +45,28 @@
"@node-rs/argon2": "^2.0.2",
"@node-rs/crc32": "^1.10.6",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^2.7.1",
"@opentelemetry/exporter-prometheus": "^0.218.0",
"@opentelemetry/exporter-zipkin": "^2.7.1",
"@opentelemetry/host-metrics": "^0.38.3",
"@opentelemetry/instrumentation": "^0.218.0",
"@opentelemetry/instrumentation-graphql": "^0.66.0",
"@opentelemetry/instrumentation-http": "^0.218.0",
"@opentelemetry/instrumentation-ioredis": "^0.66.0",
"@opentelemetry/instrumentation-nestjs-core": "^0.64.0",
"@opentelemetry/instrumentation-socket.io": "^0.65.0",
"@opentelemetry/resources": "^2.7.1",
"@opentelemetry/sdk-metrics": "^2.7.1",
"@opentelemetry/sdk-node": "^0.218.0",
"@opentelemetry/sdk-trace-node": "^2.7.1",
"@opentelemetry/semantic-conventions": "^1.38.0",
"@opentelemetry/core": "^2.8.0",
"@opentelemetry/exporter-prometheus": "^0.219.0",
"@opentelemetry/exporter-zipkin": "^2.8.0",
"@opentelemetry/host-metrics": "^0.39.0",
"@opentelemetry/instrumentation": "^0.219.0",
"@opentelemetry/instrumentation-graphql": "^0.67.0",
"@opentelemetry/instrumentation-http": "^0.219.0",
"@opentelemetry/instrumentation-ioredis": "^0.67.0",
"@opentelemetry/instrumentation-nestjs-core": "^0.65.0",
"@opentelemetry/instrumentation-socket.io": "^0.66.0",
"@opentelemetry/resources": "^2.8.0",
"@opentelemetry/sdk-metrics": "^2.8.0",
"@opentelemetry/sdk-node": "^0.219.0",
"@opentelemetry/sdk-trace-base": "^2.8.0",
"@opentelemetry/sdk-trace-node": "^2.8.0",
"@opentelemetry/semantic-conventions": "^1.41.1",
"@prisma/client": "^6.6.0",
"@prisma/instrumentation": "^6.7.0",
"@queuedash/api": "^3.16.0",
"@react-email/components": "^0.5.7",
"@socket.io/redis-adapter": "^8.3.0",
"bullmq": "5.77.6",
"bullmq": "^5.79.0",
"commander": "^13.1.0",
"cookie-parser": "^1.4.7",
"cross-env": "^10.1.0",
@@ -83,7 +84,7 @@
"html-validate": "^9.0.0",
"htmlrewriter": "^0.0.12",
"http-errors": "^2.0.0",
"ioredis": "^5.8.2",
"ioredis": "^5.11.1",
"is-mobile": "^5.0.0",
"jose": "^6.1.3",
"jsonwebtoken": "^9.0.3",
@@ -1,3 +1,6 @@
/**
* @vitest-environment happy-dom
*/
import { beforeEach, describe, expect, test, vi } from 'vitest';
const { mermaidRender, typstRender } = vi.hoisted(() => ({
@@ -5,14 +8,30 @@ const { mermaidRender, typstRender } = vi.hoisted(() => ({
typstRender: vi.fn(),
}));
const { domPurifySanitize } = vi.hoisted(() => ({
domPurifySanitize: vi.fn((value: unknown) => {
const { domPurifySanitize, sanitizeSvgForMock } = vi.hoisted(() => {
const sanitizeSvgForMock = (value: unknown) => {
if (typeof value !== 'string') {
return '';
}
return value.replace(/<script[\s\S]*?<\/script>/gi, '');
}),
}));
if (
typeof DOMParser === 'undefined' ||
typeof XMLSerializer === 'undefined'
) {
return value;
}
const doc = new DOMParser().parseFromString(value, 'image/svg+xml');
doc.querySelectorAll('script').forEach(element => {
element.remove();
});
return new XMLSerializer().serializeToString(doc.documentElement);
};
return {
domPurifySanitize: vi.fn(sanitizeSvgForMock),
sanitizeSvgForMock,
};
});
vi.mock(
'@affine/core/modules/code-block-preview-renderer/platform-backend',
@@ -33,12 +52,7 @@ import { renderMermaidSvg, renderTypstSvg, sanitizeSvg } from './bridge';
describe('preview render bridge', () => {
beforeEach(() => {
vi.clearAllMocks();
domPurifySanitize.mockImplementation((value: unknown) => {
if (typeof value !== 'string') {
return '';
}
return value.replace(/<script[\s\S]*?<\/script>/gi, '');
});
domPurifySanitize.mockImplementation(sanitizeSvgForMock);
});
test('uses worker renderers and sanitizes preview svg output', async () => {
@@ -21,7 +21,7 @@
"express-rate-limit": "^7.1.5",
"fs-extra": "^11.3.0",
"lodash-es": "^4.17.23",
"multer": "^2.0.2",
"multer": "^2.2.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react-markdown": "^10.1.0",
+374 -472
View File
File diff suppressed because it is too large Load Diff