Guard Yaml
YAML content-safety guard — defends against the type-coercion, deserialization, and DoS catalog operators face when accepting YAML sourced from user input. All detection runs at the SOURCE level: the operator's downstream parser may be PyYAML, SnakeYAML, js-yaml, libyaml, or another implementation, and the guard refuses hostile inputs before any parser sees them.
Tag-injection RCE defense: language-specific deserialization tag prefixes (!!python/ / !!java. / !!ruby/ / !!perl/ / !!js/ / !!cs/ / !!net/ / !!system.) plus the !!apply / !!new / !!eval / !!exec family are refused regardless of profile under strict. CVE coverage: CVE-2026-24009 Docling/PyYAML unsafe load, CVE-2025-68664 LangChain deserialization, CVE-2022- 1471 SnakeYAML constructor RCE, CVE-2020-1747 / CVE-2020-14343 PyYAML FullLoader, CVE-2017-18342 python/object/apply.
YAML 1.1 vs 1.2 type-coercion attacks: PyYAML and libyaml still default to YAML 1.1 in 2026, which treats unquoted no / yes / y / n / on / off as booleans (the "Norway problem" — country code "NO" parses as false), and 0777-shaped numerics parse as octal. These shapes are flagged at the source so operators can refuse silently coerced values.
Anchor-bomb (billion laughs) detection: &anchor declares, *alias references, recursive aliasing amplifies a small input into GiB on parse. Caps via maxAnchors + maxAliasDepth + maxNodes, plus an explicit alias-amplification ratio (aliases / anchors >= 8 fires alias-explosion) catches the exponential expansion shape independent of absolute counts. CVE-2026-27807 MarkUs / CVE-2025-61301 / CVE-2025-61303 ("Laughter in the Wild" — 14 libraries / 10 languages) exemplify the family.
Custom-tag exec surface: local !Foo and global !!Bar user tags suggest a non-safe parser is downstream even when the tag isn't on the language-specific deserialization denylist. Flagged per profile.
Merge-key chain DoS: <<: *anchor invokes the YAML 1.1 merge- key spec; chains of merge keys against deeply nested anchors are an additional anchor-chain expansion vector.
Multi-document streams: operators expecting a single doc silently receive only the first one and ignore the rest — hostile content in subsequent docs slips past validation that ran on the first. The guard refuses multiDocPolicy === "reject" and caps via maxDocuments.
Duplicate-key smuggling, BOM placement, and bidi / null / control / zero-width character threats route through the same shared detector backing the guard-json / guard-csv families.
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.
b.guardYaml.validate(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
tagPolicy: "reject"|"audit"|"allow",
aliasPolicy: "reject"|"audit"|"allow",
multiDocPolicy: "reject"|"audit"|"allow",
norwayPolicy: "reject"|"audit"|"allow",
leadingZeroPolicy: "reject"|"audit"|"allow",
duplicateKeyPolicy: "reject"|"audit"|"allow",
mergeKeyPolicy: "reject"|"audit"|"allow",
bidiPolicy: "reject"|"strip"|"audit"|"allow",
controlPolicy: "reject"|"strip"|"allow",
nullBytePolicy: "reject"|"strip"|"allow",
zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
safeCoreTagsAllowed: boolean,
maxBytes: number, // total source byte cap
maxDepth: number, // recursion depth cap
maxAnchors: number, // anchor declaration cap
maxAliasDepth: number, // alias-chain depth cap
maxDocuments: number, // multi-document doc count cap
maxNodes: number, // total node count cap
maxScalarLength: number, // per-scalar length cap
}
Inspect input (string of YAML source) for the full guard-yaml threat catalog without committing to a parsed value. Returns { ok, issues } where issues is the aggregated detector output — every dangerous-tag prefix, custom-tag use, anchor / alias amplification, multi-document split, Norway- problem implicit boolean, leading-zero octal, merge-key chain, duplicate-key smuggle, codepoint-class threat, and parse failure is reported with kind / severity / ruleId / snippet. Profile-driven (strict / balanced / permissive) and posture- driven (hipaa / pci-dss / gdpr / soc2).
Detection runs at the source level so the operator's downstream parser (PyYAML / SnakeYAML / js-yaml / libyaml) need not be consulted to identify hostile shapes. A final pass tries the safe- yaml parser and surfaces parse failure as a critical issue.
var rv = b.guardYaml.validate("!!python/object/new:cls\nargs: [x]\n", {
profile: "strict",
});
rv.ok; // → false
rv.issues.some(function (i) { return i.kind === "dangerous-tag"; }); // → true
b.guardYaml.parse(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
tagPolicy: "reject"|"audit"|"allow",
aliasPolicy: "reject"|"audit"|"allow",
maxBytes: number, maxDepth: number, maxNodes: number,
}
Parse input (string of YAML source) into a JavaScript value after the guard-yaml threat catalog clears. Runs the full validate-shape detector, throws GuardYamlError on the first critical issue (dangerous tag, alias-explosion, multi-document under reject, parse failure, etc.), then routes through the safe- yaml parser with the configured maxBytes / maxDepth / maxNodes caps.
The throw-on-critical pre-flight is what distinguishes guarded parse from a raw yaml-library load(): the operator's downstream code never sees deserialization-tag instantiation, billion-laughs expansion, or duplicate-key smuggling because the source is refused before the parser runs.
var safe = b.guardYaml.parse("name: alice\nage: 30\n", {
profile: "strict",
});
safe.name; // → "alice"
safe.age; // → 30
b.guardYaml.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 GuardYamlError with code "yaml.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.guardYaml.compliancePosture("hipaa");
posture; // → overlay clone (mutable)
try {
b.guardYaml.compliancePosture("not-a-regime");
} catch (e) {
e.code; // → "yaml.bad-posture"
}
b.guardYaml.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.guardYaml.buildProfile({ extends: "strict" });
custom; // → composed profile object
b.guardYaml.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 GuardYamlError with code "yaml.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.guardYaml.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id; // → "tenant-policy"
b.guardYaml.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.guardYaml.gate({ profile: "strict" });
var decision = await gate.check({ bytes: Buffer.from("...") });
decision.action; // → "serve" | "refuse" | …
b.guardYaml.sanitize(input, opts?) #
{
profile: string, // one of PROFILES; default this guard's default
compliancePosture: string, // overlay one of hipaa/pci-dss/gdpr/soc2
}
Return a normalized form of input when no high / critical issue fires; throw GuardYamlError on any such refusal (best-effort repair, never a silent pass). Resolves the profile + posture, runs the guard's detection, throws via gateContract.throwOnRefusalSeverity on a refusal, then applies the guard's own safe transform. Wired by gateContract.defineGuard, so the resolve → detect → throw → transform order is identical across the family; a guard with no safe transform ships no sanitize.
var safe = b.guardYaml.sanitize(input, { profile: "permissive" });
safe; // → normalized value
b.guardYaml.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 GuardYamlError with code "yaml.bad-opt" / "yaml.bad-posture" on an unknown profile or posture name.
var resolved = b.guardYaml.resolveOpts({ profile: "strict" });
resolved.profile; // → "strict"
Last updated 2026-08-08T16:39:49.652Z by seeder.