diff --git a/bun.lock b/bun.lock index bf7c62af..a1120651 100644 --- a/bun.lock +++ b/bun.lock @@ -2566,7 +2566,7 @@ "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], - "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + "fast-uri": ["fast-uri@3.1.8", "", {}, "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg=="], "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="], @@ -3894,7 +3894,7 @@ "source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], @@ -4380,7 +4380,7 @@ "@antfu/install-pkg/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], - "@antv/infographic/postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + "@antv/infographic/postcss": ["postcss@8.5.29", "", { "dependencies": { "nanoid": "^3.3.19", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ=="], "@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -4522,7 +4522,7 @@ "@iconify/tools/domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - "@iconify/tools/svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="], + "@iconify/tools/svgo": ["svgo@3.3.5", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-8SQMzdrvWaD8deUmrnYB+ASyxBVgWUOilg+A75nE/76WdLpj6LopCwiAVvkzkcqy/9b7t2Mg7faFLjg0ZRcZ3w=="], "@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -5316,7 +5316,7 @@ "zip-dir/jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], - "@antv/infographic/postcss/nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + "@antv/infographic/postcss/nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], "@astrojs/check/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/scripts/dependency-security.test.mjs b/scripts/dependency-security.test.mjs index 7c4dbef0..9e2d5aaf 100644 --- a/scripts/dependency-security.test.mjs +++ b/scripts/dependency-security.test.mjs @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createRequire } from "node:module"; -import { mkdtempSync, rmSync } from "node:fs"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -8,12 +8,73 @@ const desktopRequire = createRequire(new URL("../apps/desktop/package.json", imp const rootRequire = createRequire(new URL("../package.json", import.meta.url)); const mobileRequire = createRequire(new URL("../apps/mobile/package.json", import.meta.url)); const extensionRequire = createRequire(new URL("../apps/extension/package.json", import.meta.url)); +const webRequire = createRequire(new URL("../apps/web/package.json", import.meta.url)); +const siteRequire = createRequire(new URL("../apps/site/package.json", import.meta.url)); function consumerRequire(start, ...packages) { return packages.reduce((require, name) => createRequire(require.resolve(name)), start); } describe("dependency security at the actual consumer", () => { + test("the desktop schema validator canonicalizes percent-encoded hostnames", () => { + const builderRequire = consumerRequire(desktopRequire, "electron-builder", "app-builder-lib"); + const ajvRequire = consumerRequire(builderRequire, "ajv"); + const uri = ajvRequire("fast-uri"); + expect(uri.parse("//%41.com").host).toBe("a.com"); + expect(uri.equal("//%41.com", "//a.com")).toBe(true); + const Ajv = builderRequire("ajv"); + const validate = new Ajv().compile({ type: "object", properties: { enabled: { type: "boolean" } } }); + expect(validate({ enabled: true })).toBe(true); + expect(validate({ enabled: "true" })).toBe(false); + }); + + test("infographic CSS cannot load an absolute source map without a source filename", () => { + const infographicRequire = consumerRequire(webRequire, "@antv/infographic/package.json"); + const postcss = infographicRequire("postcss"); + const directory = mkdtempSync(join(tmpdir(), "edgeever-css-map-")); + const mapPath = join(directory, "private.map"); + try { + writeFileSync(mapPath, JSON.stringify({ version: 3, sources: ["private.txt"], sourcesContent: ["EDGE_EVER_PRIVATE_MAP_CONTENT"], names: [], mappings: "AAAA" })); + const result = postcss([]).process(`a { color: green }\n/*# sourceMappingURL=${mapPath} */`, { map: { inline: false } }).sync(); + expect(result.root.source.input.map?.text).toBeUndefined(); + expect(JSON.stringify(result.map.toJSON())).not.toContain("EDGE_EVER_PRIVATE_MAP_CONTENT"); + expect(result.css).toContain("color: green"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } + }); + + test("Tailwind's source map consumer rejects excessive indexed offsets", () => { + const tailwindRequire = consumerRequire(rootRequire, "@tailwindcss/vite", "@tailwindcss/node"); + const { SourceMapConsumer } = tailwindRequire("source-map-js"); + const map = { version: 3, sources: ["input.css"], names: [], mappings: "AAAA" }; + expect(new SourceMapConsumer(map).originalPositionFor({ line: 1, column: 0 }).source).toBe("input.css"); + expect(() => new SourceMapConsumer({ version: 3, sections: [{ offset: { line: 10000001, column: 0 }, map }] })).toThrow(/offset/i); + }); + + test("infographic's Nano ID handles non-positive sizes without looping", () => { + const postcssRequire = consumerRequire(webRequire, "@antv/infographic/package.json", "postcss"); + const version = postcssRequire("nanoid/package.json").version; + const npmRequire = consumerRequire(desktopRequire, "npm/package.json"); + expect(npmRequire("semver").satisfies(version, ">=3.3.18 <4")).toBe(true); + const { nanoid, customAlphabet } = postcssRequire("nanoid/non-secure"); + expect(nanoid(-1)).toBe(""); + expect(customAlphabet("abc")(0)).toBe(""); + expect(customAlphabet("abc")(8)).toMatch(/^[abc]{8}$/); + expect(postcssRequire("nanoid").customAlphabet("abc")(0)).toBe(""); + }); + + test("Iconify's SVGO strips executable namespace-prefixed and obfuscated links", () => { + const iconifyRequire = consumerRequire(siteRequire, "astro-icon/package.json", "@iconify/tools/package.json"); + const { optimize } = iconifyRequire("svgo"); + const svg = 'alert(1)'; + const output = optimize(svg, { plugins: ["removeScriptElement"] }).data; + expect(output).not.toContain("script"); + expect(output).not.toContain("onload"); + expect(output).toContain('r="2"'); + expect(output).toContain('r="3"'); + }); + test("native plist consumers preserve values and reject injected entity names", () => { const expoRequire = consumerRequire(mobileRequire, "expo/package.json", "@expo/config-plugins"); const builderRequire = consumerRequire(desktopRequire, "electron-builder", "app-builder-lib");