3.6 KiB
Security model
SVG is active content. Threats include script/event execution, navigation,
external fetches and tracking, CSS resource resolution, foreignObject, native
animation triggers, namespace confusion, entity/DOCTYPE processing, parser
differentials, oversized compressed/data content and algorithmic complexity.
Source and projection
Canonical source is inert text and remains available even when unsafe. A separate projection is built from a cloned semantic document. DOMPurify's SVG profiles are followed by an application policy that:
- removes
script,foreignObject,animate,animateMotion,animateTransform,set, foreign namespaces and allon*attributes; - disables links/navigation and executable or external URL attributes;
- allows only unique local fragment references and bounded base64 raster image
data (
png,jpeg,gif,webp,avif); - parses style declarations/stylesheets with css-tree and rejects
@import, unsafe legacy properties, parse failures, non-local URLs,expression()and resource-producing functions such asimage-set()andpaint(); - reports findings with source ranges wherever a semantic node/range exists.
DOCTYPE is excluded before DOM parsing; entity declarations invalidate the semantic projection. Sanitized export removes temporary mapping attributes. Sanitize is never applied to canonical source without preview and acceptance.
Isolated preview
The projection is an iframe srcdoc with sandbox="allow-scripts" and no
allow-same-origin, giving it an opaque origin. Its child CSP is:
default-src 'none'; connect-src 'none'; object-src 'none'; frame-src 'none';
base-uri 'none'; form-action 'none'; img-src data: blob:;
style-src 'unsafe-inline'; script-src <controller-origin>
The sole script is the packaged canvas-frame-controller.js. It is loaded with
anonymous CORS, accepts messages only from parent with a random channel,
validates message fields and removes its script element. The parent validates
both event.source and channel.
Because a sandbox without same-origin treats the script request as cross-origin, serve this one app-relative asset with:
Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-origin
Keep Cross-Origin-Resource-Policy: same-origin for other files. Use an Nginx
map/header value, not a nested location that would drop inherited security
headers. Toolbox Portal 0.10.0 implements and tests this exception.
Limits and tests
Hard limits are defined in src/app/limits.ts. SVGZ uses streaming
decompression and stops once output crosses the source limit. Workers are
revisioned, timed out and cancelable. Tree/diagnostic rendering is capped.
Project-authored tests cover script, handlers, links, external image/use/filter, CSS imports/URLs/parser failures/resource functions, native animation, namespaces, duplicate IDs/cycles, malformed entities/XML, depth/attribute/path and data limits. Browser tests assert no script callback and no request to the hostile domain.
Limitations and reporting
This policy protects this application's projections and exports; it is not a general guarantee for arbitrary downstream embedding. Browser SVG/CSS parsers and DOMPurify remain dependency/security boundaries. Bounded data URLs may still decode expensive images within the raster-pixel limits. CSP header regressions can break the fixed controller even when the content remains inert.
Report vulnerabilities privately through the repository owner/contact before opening a public issue when disclosure could expose users. Include the SVG, browser, deployment headers and observed network/execution behavior without sensitive user files.