Guard Svg
SVG content-safety primitive — defends against XXE / billion-laughs entity expansion, SSRF via xlink:href, animation-href injection (the retroactive-poisoning class), embedded / namespace-shift escape hatches, dangerous URL schemes, CSS injection in style attributes, SVGZ compressed payloads, and Trojan-Source bidi / zero-width / null-byte threats.
Element + attribute allowlist with strict default (text + shape primitives only). Profiles strict / balanced / permissive compose with compliance postures hipaa / pci-dss / gdpr / soc2. Integrates with b.fileUpload and b.staticServe's contentSafety hook by default.
Source-of-truth references: Fortinet anatomy of SVG attack surface; Angular GHSA-jrmj-c5cx-3cw6 + GHSA-v4hv-rgfq-gp49 SVG animation/href XSS; SVGO CVE-2026-29074 billion-laughs DoS; siyuan-note GHSA-5hc8-qmg8-pw27 animate-element sanitizer bypass; cure53/DOMPurify issue #233 xlink:href filtering; insertScript SVG fun-time series; svg2raster-cheatsheet SSRF guide.
Threat catalog covered:
1. Dangerous SVG tags — ', { profile: "strict" }); rv.ok; // → false rv.issues[0].kind; // → "dangerous-tag" rv.issues[0].severity; // → "critical" var clean = b.guardSvg.validate( '', { profile: "strict" }); clean.ok; // → true clean.issues.length; // → 0
b.guardSvg.sanitize(input, opts) #
{
profile: "strict" | "balanced" | "permissive",
compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
allowedTags: Array,
urlSchemes: Array,
allowImageData: boolean,
allowExternalRefs: boolean,
allowAnimation: boolean,
maxBytes: number,
}
Best-effort sanitizer. Strips dangerous tags (, , plugin embeds, animation elements when the profile forbids them), event-handler attributes (every /^on[a-z]/), URL attributes carrying javascript: / vbscript: / non-allowlisted schemes, CSS injection inside style="...", DOCTYPE / / processing instructions / CDATA, bidi / control / null-byte / zero-width threats per the profile's char policies. Throws GuardSvgError (svg.svgz) on SVGZ input — operators must ungzip first then re-sanitize.
var safe = b.guardSvg.sanitize(
'',
{ profile: "balanced" });
safe;
// → ''
// Event-handler attributes are stripped:
var clean = b.guardSvg.sanitize(
'',
{ profile: "strict" });
/onload/.test(clean); // → false
b.guardSvg.gate(opts) #
{
profile: "strict" | "balanced" | "permissive",
compliancePosture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
mode: "enforce" | "audit-only",
audit: AuditEmitter,
observability: ObservabilityEmitter,
forensicEvidenceStore: ForensicStore,
allowedTags: Array,
urlSchemes: Array,
allowExternalRefs: boolean,
allowAnimation: boolean,
maxBytes: number,
maxRuntimeMs: number,
}
Build a uniform gate over the guard-* family contract. Returns a gate whose async check(ctx) produces a verdict { ok, action, issues?, sanitized? } where action is serve / audit-only / sanitize / refuse. SVGZ inputs always refuse — operators ungzip and re-gate the inner SVG. External xlink:href on / refuses under strict (SSRF + XSS chain). Sanitize path is taken when no policy is set to reject and the issue set is repairable.
var g = b.guardSvg.gate({ profile: "strict" });
var verdict = await g.check({
bytes: Buffer.from('', "utf8"),
});
verdict.action; // → "serve"
// Refuses external xlink:href under strict:
var refuse = await g.check({
bytes: Buffer.from(
'',
"utf8"),
});
refuse.action; // → "refuse"
b.guardSvg.compliancePosture(name) #
Look up a compliance-posture overlay by name (one of "hipaa" / "pci-dss" / "gdpr" / "soc2"). Returns a fresh clone of the posture overlay so the caller may mutate it freely without disturbing the shared table. Throws GuardSvgError with code "svg.bad-posture" when the name is not one this guard maps. Wired by gateContract.defineGuard through gateContract.lookupCompliancePosture, so the clone semantics and error code are identical across every guard in the family.
var posture = b.guardSvg.compliancePosture("hipaa");
posture; // → overlay clone (mutable)
try {
b.guardSvg.compliancePosture("not-a-regime");
} catch (e) {
e.code; // → "svg.bad-posture"
}
b.guardSvg.buildProfile(opts) #
{
extends: string|string[], // base profile name(s) to compose
...: any guard key, // inline override of resolved keys
}
Compose a derived profile from one or more named bases plus inline overrides, resolving names through this guard's own PROFILES table. opts.extends is a base profile name ("strict" / "balanced" / "permissive") or an array of names — later entries shadow earlier ones, and inline opts keys win last. Wired by gateContract.defineGuard through gateContract.makeProfileBuilder, so operator-defined profiles stay traceable to a baseline instead of a hand-typed dictionary.
var custom = b.guardSvg.buildProfile({ extends: "strict" });
custom; // → composed profile object
b.guardSvg.loadRulePack(pack) #
Register an operator-supplied rule pack with this guard's rule-pack registry. The pack is identified by pack.id (a non-empty string) and stored for later dispatch by gates that opt in via opts.rulePackId. Returns the pack unchanged on success; throws GuardSvgError with code "svg.bad-opt" when pack is missing or pack.id is not a non-empty string. Wired by gateContract.defineGuard through gateContract.makeRulePackLoader, so storage shape and validation are identical across the family.
var pack = b.guardSvg.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id; // → "tenant-policy"
b.guardSvg.resolveOpts(opts?) #
{
profile: string, // one of PROFILES; default this guard's default
compliancePosture: string, // overlay one of hipaa/pci-dss/gdpr/soc2
}
Resolve caller opts against this guard's PROFILES + compliance-posture overlays into the fully-defaulted option set the guard runs on — the same resolution validate / sanitize / gate apply internally. Wired by gateContract.defineGuard from the guard's binding config (profiles / postures / defaults / error class), so a guard's bespoke gate calls resolveOpts instead of re-declaring the per-guard resolver wrapper. Throws GuardSvgError with code "svg.bad-opt" / "svg.bad-posture" on an unknown profile or posture name.
var resolved = b.guardSvg.resolveOpts({ profile: "strict" });
resolved.profile; // → "strict"
Last updated 2026-08-08T16:39:49.652Z by seeder.