mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-01 17:50:50 +08:00
fix: deps & config (#15126)
This commit is contained in:
@@ -31,7 +31,6 @@
|
|||||||
"groupSlug": "all-minor-patch",
|
"groupSlug": "all-minor-patch",
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"matchManagers": ["npm"],
|
"matchManagers": ["npm"],
|
||||||
"matchPackageNames": ["*"],
|
|
||||||
"excludePackagePatterns": ["^@blocksuite/", "^oxlint$"]
|
"excludePackagePatterns": ["^@blocksuite/", "^oxlint$"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ describe('sanitizeSvg', () => {
|
|||||||
expect(sanitizeSvg('<div><svg></svg></div>')).toBe('');
|
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', () => {
|
test('keeps internal glyph references and safe image data urls', () => {
|
||||||
const sanitized = sanitizeSvg(`
|
const sanitized = sanitizeSvg(`
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ const SAFE_IMAGE_DATA_URL_PATTERN =
|
|||||||
/^data:image\/(?:png|jpe?g|gif|webp|svg\+xml);base64,[a-z0-9+/=]+$/i;
|
/^data:image\/(?:png|jpe?g|gif|webp|svg\+xml);base64,[a-z0-9+/=]+$/i;
|
||||||
const UNSAFE_CSS_PATTERN =
|
const UNSAFE_CSS_PATTERN =
|
||||||
/(?:url\s*\(|@import|javascript\s*:|expression\s*\(|-moz-binding)/i;
|
/(?: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 = [
|
const SVG_ROOT_ATTRIBUTES = [
|
||||||
'class',
|
'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) {
|
function getOriginalSvgRoot(svg: string, parser: DOMParser) {
|
||||||
const root = parser.parseFromString(svg, 'image/svg+xml').documentElement;
|
const root = parser.parseFromString(svg, 'image/svg+xml').documentElement;
|
||||||
if (root?.tagName.toLowerCase() === 'svg') {
|
if (root?.tagName.toLowerCase() === 'svg') {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
if (!SVG_ROOT_PATTERN.test(svg)) {
|
if (!hasSvgRoot(svg)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return parser.parseFromString(svg, 'text/html').querySelector('svg');
|
return parser.parseFromString(svg, 'text/html').querySelector('svg');
|
||||||
@@ -79,7 +125,7 @@ function ensureSvgRoot(
|
|||||||
sanitized: string,
|
sanitized: string,
|
||||||
parser: DOMParser
|
parser: DOMParser
|
||||||
) {
|
) {
|
||||||
if (SVG_ROOT_PATTERN.test(sanitized)) {
|
if (hasSvgRoot(sanitized)) {
|
||||||
const sanitizedDoc = parser.parseFromString(sanitized, 'image/svg+xml');
|
const sanitizedDoc = parser.parseFromString(sanitized, 'image/svg+xml');
|
||||||
const sanitizedRoot = sanitizedDoc.documentElement;
|
const sanitizedRoot = sanitizedDoc.documentElement;
|
||||||
return sanitizedRoot?.tagName.toLowerCase() === 'svg'
|
return sanitizedRoot?.tagName.toLowerCase() === 'svg'
|
||||||
@@ -228,7 +274,7 @@ function sanitizeSvgWithDepth(
|
|||||||
) {
|
) {
|
||||||
const sanitized = DOMPurify.sanitize(svg, svgConfig);
|
const sanitized = DOMPurify.sanitize(svg, svgConfig);
|
||||||
|
|
||||||
if (typeof sanitized !== 'string' || !SVG_ROOT_PATTERN.test(sanitized)) {
|
if (typeof sanitized !== 'string' || !hasSvgRoot(sanitized)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return sanitized.trim();
|
return sanitized.trim();
|
||||||
|
|||||||
+16
-1
@@ -167,7 +167,22 @@
|
|||||||
"typedarray": "npm:@nolyfill/typedarray@^1",
|
"typedarray": "npm:@nolyfill/typedarray@^1",
|
||||||
"macos-alias": "npm:@napi-rs/macos-alias@0.0.4",
|
"macos-alias": "npm:@napi-rs/macos-alias@0.0.4",
|
||||||
"fs-xattr": "npm:@napi-rs/xattr@latest",
|
"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",
|
"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",
|
"@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"
|
"yjs": "patch:yjs@npm%3A13.6.21#~/.yarn/patches/yjs-npm-13.6.21-c9f1f3397c.patch"
|
||||||
|
|||||||
@@ -45,27 +45,28 @@
|
|||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"@node-rs/crc32": "^1.10.6",
|
"@node-rs/crc32": "^1.10.6",
|
||||||
"@opentelemetry/api": "^1.9.0",
|
"@opentelemetry/api": "^1.9.0",
|
||||||
"@opentelemetry/core": "^2.7.1",
|
"@opentelemetry/core": "^2.8.0",
|
||||||
"@opentelemetry/exporter-prometheus": "^0.218.0",
|
"@opentelemetry/exporter-prometheus": "^0.219.0",
|
||||||
"@opentelemetry/exporter-zipkin": "^2.7.1",
|
"@opentelemetry/exporter-zipkin": "^2.8.0",
|
||||||
"@opentelemetry/host-metrics": "^0.38.3",
|
"@opentelemetry/host-metrics": "^0.39.0",
|
||||||
"@opentelemetry/instrumentation": "^0.218.0",
|
"@opentelemetry/instrumentation": "^0.219.0",
|
||||||
"@opentelemetry/instrumentation-graphql": "^0.66.0",
|
"@opentelemetry/instrumentation-graphql": "^0.67.0",
|
||||||
"@opentelemetry/instrumentation-http": "^0.218.0",
|
"@opentelemetry/instrumentation-http": "^0.219.0",
|
||||||
"@opentelemetry/instrumentation-ioredis": "^0.66.0",
|
"@opentelemetry/instrumentation-ioredis": "^0.67.0",
|
||||||
"@opentelemetry/instrumentation-nestjs-core": "^0.64.0",
|
"@opentelemetry/instrumentation-nestjs-core": "^0.65.0",
|
||||||
"@opentelemetry/instrumentation-socket.io": "^0.65.0",
|
"@opentelemetry/instrumentation-socket.io": "^0.66.0",
|
||||||
"@opentelemetry/resources": "^2.7.1",
|
"@opentelemetry/resources": "^2.8.0",
|
||||||
"@opentelemetry/sdk-metrics": "^2.7.1",
|
"@opentelemetry/sdk-metrics": "^2.8.0",
|
||||||
"@opentelemetry/sdk-node": "^0.218.0",
|
"@opentelemetry/sdk-node": "^0.219.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^2.7.1",
|
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.38.0",
|
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
||||||
|
"@opentelemetry/semantic-conventions": "^1.41.1",
|
||||||
"@prisma/client": "^6.6.0",
|
"@prisma/client": "^6.6.0",
|
||||||
"@prisma/instrumentation": "^6.7.0",
|
"@prisma/instrumentation": "^6.7.0",
|
||||||
"@queuedash/api": "^3.16.0",
|
"@queuedash/api": "^3.16.0",
|
||||||
"@react-email/components": "^0.5.7",
|
"@react-email/components": "^0.5.7",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
"bullmq": "5.77.6",
|
"bullmq": "^5.79.0",
|
||||||
"commander": "^13.1.0",
|
"commander": "^13.1.0",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
"html-validate": "^9.0.0",
|
"html-validate": "^9.0.0",
|
||||||
"htmlrewriter": "^0.0.12",
|
"htmlrewriter": "^0.0.12",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.0",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.11.1",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
"jose": "^6.1.3",
|
"jose": "^6.1.3",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @vitest-environment happy-dom
|
||||||
|
*/
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
const { mermaidRender, typstRender } = vi.hoisted(() => ({
|
const { mermaidRender, typstRender } = vi.hoisted(() => ({
|
||||||
@@ -5,14 +8,30 @@ const { mermaidRender, typstRender } = vi.hoisted(() => ({
|
|||||||
typstRender: vi.fn(),
|
typstRender: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { domPurifySanitize } = vi.hoisted(() => ({
|
const { domPurifySanitize, sanitizeSvgForMock } = vi.hoisted(() => {
|
||||||
domPurifySanitize: vi.fn((value: unknown) => {
|
const sanitizeSvgForMock = (value: unknown) => {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return '';
|
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(
|
vi.mock(
|
||||||
'@affine/core/modules/code-block-preview-renderer/platform-backend',
|
'@affine/core/modules/code-block-preview-renderer/platform-backend',
|
||||||
@@ -33,12 +52,7 @@ import { renderMermaidSvg, renderTypstSvg, sanitizeSvg } from './bridge';
|
|||||||
describe('preview render bridge', () => {
|
describe('preview render bridge', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
domPurifySanitize.mockImplementation((value: unknown) => {
|
domPurifySanitize.mockImplementation(sanitizeSvgForMock);
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return value.replace(/<script[\s\S]*?<\/script>/gi, '');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uses worker renderers and sanitizes preview svg output', async () => {
|
test('uses worker renderers and sanitizes preview svg output', async () => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"express-rate-limit": "^7.1.5",
|
"express-rate-limit": "^7.1.5",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"lodash-es": "^4.17.23",
|
"lodash-es": "^4.17.23",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.2.0",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
"react-dom": "^19.2.1",
|
"react-dom": "^19.2.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user