Guard Template
Server-Side Template Injection (SSTI) content-safety guard — refuses user-supplied strings that contain template-engine syntax BEFORE they're rendered. Template-injection vulnerable surfaces escape sandboxes through engine helpers (render, lookup, with, attr_filter); the safe shape is "logic-less templates only, untrusted strings are data not code". This primitive enforces that boundary by refusing engine syntax in any operator-untrusted input. Pair with logic-less Mustache helpers, Handlebars noEscape: false, and Liquid's strict- variables mode so the framework's defense-in-depth holds even when an operator forgets to escape. KIND=identifier; the gate consumes ctx.identifier (or ctx.text) and refuses on hostile shapes.
Threat catalog (engine-shape detection): Jinja2 / Django / Twig / Liquid / Handlebars / Mustache / AngularJS — {{...}} expressions and {%...%} statements (CVE-2024-22195 Jinja xml_attr_filter, CVE-2024-26139 Bottle, CVE-2024-23348 Pyrogram); ERB / Tornado — <%...%> and <%=...%>; Pug — #{...} interpolation and !{...} raw-HTML interpolation (prototype-pollution exit when the model is operator-fed); Mako / Velocity / Tornado / JS template-literal — ${...} interpolation; Velocity directives (#set, #if, #foreach, #parse, #include); BIDI / null / C0 control / zero-width universal refuse.
Profiles: strict / balanced / permissive. Compliance postures: hipaa / pci-dss / gdpr / soc2. Operators select via { profile: "strict" } or { compliancePosture: "hipaa" }; postures overlay on top of the profile baseline. Jinja / ERB / Pug shape rejection holds at every profile — the SSTI class is never an operator opt-in.
Template input cannot be repaired safely (stripping {{ from {{name}} produces a different document); sanitize either passes through clean input or throws GuardTemplateError; the gate returns serve / audit-only / refuse (no sanitize action). The ${...} and Velocity-directive policies default to audit outside strict because they overlap with legitimate JS / shell substrings, so operators tune via overrides.
b.guardTemplate.validate(input, opts) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
bidiPolicy: "reject"|"audit"|"allow",
controlPolicy: "reject"|"audit"|"allow",
nullBytePolicy: "reject"|"audit"|"allow",
zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
jinjaPolicy: "reject"|"audit"|"allow",
erbPolicy: "reject"|"audit"|"allow",
pugPolicy: "reject"|"audit"|"allow",
dollarBracePolicy: "reject"|"audit"|"allow",
velocityDirectivePolicy: "reject"|"audit"|"allow",
maxBytes: number,
maxRuntimeMs: number,
}
Inspect a user-supplied template-rendering input and return an aggregated issue list. Pure inspection — never throws on hostile input; caller decides what to do with the issues. The ok flag is true only when zero critical / high issues fire. Throws GuardTemplateError("template.bad-opt") when a numeric opt is non-finite / negative (config-time mistake by the operator).
var clean = b.guardTemplate.validate("Hello world", { profile: "strict" });
clean.ok; // → true
var hostile = b.guardTemplate.validate("Hello {{7*7}}", { profile: "strict" });
hostile.ok; // → false
hostile.issues.some(function (i) { return i.kind === "jinja-expression"; }); // → true
b.guardTemplate.sanitize(input, opts) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
jinjaPolicy: "reject"|"audit"|"allow",
erbPolicy: "reject"|"audit"|"allow",
pugPolicy: "reject"|"audit"|"allow",
dollarBracePolicy: "reject"|"audit"|"allow",
velocityDirectivePolicy: "reject"|"audit"|"allow",
maxBytes: number,
}
Pass-through-or-throw. Template-input strings cannot be safely repaired (stripping {{ from {{name}} produces a different document and silently changes operator intent); this primitive returns the input unchanged when no critical or high issue fires, otherwise throws GuardTemplateError with the offending rule id (e.g. template.jinja-expression, template.erb-expression, template.pug-interpolation, template.velocity-directive). Operators that need a "best- effort cleanup" semantic should pre-escape the input through the rendering engine's own escape helper instead.
var safe = b.guardTemplate.sanitize("Hello world", { profile: "strict" });
safe; // → "Hello world"
try {
b.guardTemplate.sanitize("Hello {{7*7}}", { profile: "strict" });
} catch (e) {
e.code; // → "template.jinja-expression"
}
b.guardTemplate.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 GuardTemplateError with code "template.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.guardTemplate.compliancePosture("hipaa");
posture; // → overlay clone (mutable)
try {
b.guardTemplate.compliancePosture("not-a-regime");
} catch (e) {
e.code; // → "template.bad-posture"
}
b.guardTemplate.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.guardTemplate.buildProfile({ extends: "strict" });
custom; // → composed profile object
b.guardTemplate.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 GuardTemplateError with code "template.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.guardTemplate.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id; // → "tenant-policy"
b.guardTemplate.gate(opts?) #
{
profile: string, // one of PROFILES; default this guard's default
compliancePosture: string, // overlay one of hipaa/pci-dss/gdpr/soc2
mode: string, // one of gateContract MODES; default "enforce"
}
Build the guard's request-boundary gate — a contract-shaped object exposing check(ctx) that host primitives call at their byte moment. This is the factory default chain: serve when no issue, audit-only for info / warn issues, and refuse for any high / critical issue, dispatched to the right ctx field by the guard's KIND. Wired by gateContract.defineGuard through gateContract.buildGuardGate; a guard whose gate diverges (a bespoke sanitize-and-reserialize chain, for example) ships its own gate block instead of this template.
var gate = b.guardTemplate.gate({ profile: "strict" });
var decision = await gate.check({ bytes: Buffer.from("...") });
decision.action; // → "serve" | "refuse" | …
b.guardTemplate.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 GuardTemplateError with code "template.bad-opt" / "template.bad-posture" on an unknown profile or posture name.
var resolved = b.guardTemplate.resolveOpts({ profile: "strict" });
resolved.profile; // → "strict"
Last updated 2026-08-08T16:39:49.652Z by seeder.