feat: introduce local-first SVG workbench

This commit is contained in:
2026-08-02 16:31:49 +02:00
commit 39d9802daa
97 changed files with 20702 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
import { describe, expect, it } from "vitest";
import { parseSvgSource } from "../../src/document/source-parser";
import {
createEditingProjection,
inspectSvgSecurity,
} from "../../src/security/sanitize-svg";
function parse(source: string) {
const result = parseSvgSource(source, 1);
expect(result.semantic).not.toBeNull();
return result.semantic!;
}
describe("application-owned adversarial SVG policy", () => {
it("allows only references that resolve to one unique local ID", () => {
const semantic = parse(`<svg xmlns="http://www.w3.org/2000/svg">
<defs><linearGradient id="unique"/><linearGradient id="duplicate"/><linearGradient id="duplicate"/></defs>
<rect id="ok" fill="url(#unique)"/>
<rect id="missing" fill="url(#absent)"/>
<rect id="ambiguous" fill="url(#duplicate)"/>
</svg>`);
const projection = createEditingProjection(semantic).source;
expect(projection).toContain('id="ok" fill="url(#unique)"');
expect(projection).not.toContain("url(#absent)");
expect(projection).not.toContain('id="ambiguous" fill=');
});
it("parses CSS and removes imports, external URLs and behavior-like properties", () => {
const semantic = parse(`<svg xmlns="http://www.w3.org/2000/svg">
<style>@import "https://evil.example/theme.css"; rect { fill: red }</style>
<rect id="external" style="fill: url(https://evil.example/a); stroke: red"/>
<circle id="behavior" style="behavior: url(#x)"/>
</svg>`);
const findings = inspectSvgSecurity(semantic);
expect(findings.map((item) => item.code)).toEqual(
expect.arrayContaining(["unsafe-stylesheet", "unsafe-inline-css"]),
);
const projection = createEditingProjection(semantic).source;
expect(projection).not.toContain("evil.example");
expect(projection).not.toContain("behavior:");
expect(projection).not.toContain("<style");
});
it("keeps the normal editing canvas static by stripping source animations", () => {
const semantic = parse(`<svg xmlns="http://www.w3.org/2000/svg">
<circle id="dot" r="2"><animate attributeName="opacity" values="0;1" dur="1s"/></circle>
<set attributeName="fill" to="red" begin="click"/>
</svg>`);
const findings = inspectSvgSecurity(semantic);
expect(findings.map((item) => item.code)).toEqual(
expect.arrayContaining(["blocked-animate", "blocked-set"]),
);
const projection = createEditingProjection(semantic).source;
expect(projection).not.toMatch(/<animate|<set\b/iu);
expect(semantic.source).toContain("<animate");
});
it("neutralizes executable schemes, external use targets and non-SVG element namespaces", () => {
const semantic =
parse(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:x="urn:active">
<a href="javascript:alert(1)"><rect width="1" height="1"/></a>
<use href="https://evil.example/icons.svg#mark"/>
<x:widget x:run="yes"/>
<image href="data:text/html;base64,PHNjcmlwdD4="/>
</svg>`);
const projection = createEditingProjection(semantic).source;
expect(projection).not.toMatch(
/javascript:|evil\.example|data:text\/html|x:widget/iu,
);
expect(semantic.source).toContain("javascript:");
});
});