Guard Json
JSON content-safety guard — defends against the threat catalog operators face when accepting JSON sourced from user input. b.safeJson.parse enforces baseline depth + size caps; this module layers prototype-pollution / depth-bomb / key-count / duplicate-key / unicode threat detection on top.
Prototype-pollution defense: keys __proto__ / constructor / prototype anywhere in the tree are detected at the SOURCE level (before any parser sees them). After JSON.parse normalizes the input, __proto__ routes through the prototype setter and is invisible to Object.keys(), so a post-parse tree walk misses the pollution shape — the source-text scan catches it. CVE coverage spans the 2025-2026 deserialization + prototype- pollution wave: CVE-2025-55182 React Server Functions RCE, CVE-2025-57820 / CVE-2026-30226 Svelte devalue, CVE-2026-35209 defu, CVE-2026-28794 @orpc/client, CVE-2025-13465 Lodash path traversal, CVE-2025-25014 Kibana, CVE-2024-38984 json-override, CVE-2022-42743 deep-parse-json, GHSA-9c47-m6qq-7p4h JSON5.
Depth + breadth caps: maxDepth / maxKeysPerObject / maxArrayLength / maxStringLength / maxTotalNodes refuse key-count bombs (10^6 keys per object) and stack-exhaustion nesting attacks under strict.
Duplicate-key smuggling: RFC 8259 says keys SHOULD be unique; JSON.parse silently last-wins. A two-validator pipeline that inspects the first occurrence and trusts the parser's last-wins value is the smuggling shape; this guard rescans the source for identical quoted keys at the same { ... } nesting level.
JSON5 / JSONC quirks (single-line // + block C-style comments, trailing commas, NaN / Infinity / -Infinity, hex literals, single-quoted keys) — RFC 8259 forbids these but lenient parsers accept; the guard flags them at the source so operators can refuse hostile inputs regardless of which parser is downstream.
Numeric precision loss: integers above Number.MAX_SAFE_INTEGER (~9.007 x 10^15, 16 digits) silently lose precision when round- tripped through Number. Detected via raw-text scan for digit runs of 17+ characters.
BOM injection (leading or mid-stream U+FEFF) and bidi / null / control / zero-width character threats route through the shared lib/codepoint-class catalog — the same detector backing the guard-csv / guard-html / guard-svg families.
Top-level-key allowlist: when the operator opts in via topLevelKeyAllowlist: ["alpha", "beta"], every other top-level key triggers a refused-shape issue. Useful for HTTP body schemas where unexpected keys signal malformed or hostile input.
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.
Source files MUST be pure ASCII; threat-detection regexes compose programmatically via lib/codepoint-class so the source never embeds the attack characters themselves.
b.guardJson.validate(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
pollutionPolicy: "reject"|"strip"|"audit"|"allow",
duplicateKeyPolicy: "reject"|"audit"|"allow",
nanInfinityPolicy: "reject"|"audit"|"allow",
commentPolicy: "reject"|"audit"|"allow",
trailingCommaPolicy: "reject"|"audit"|"allow",
json5SyntaxPolicy: "reject"|"audit"|"allow",
bomPolicy: "reject"|"strip"|"allow",
bidiPolicy: "reject"|"strip"|"audit"|"allow",
controlPolicy: "reject"|"strip"|"allow",
nullBytePolicy: "reject"|"strip"|"allow",
zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
numericPrecisionPolicy: "reject"|"audit"|"allow",
requireTopLevelKeyAllowlist: boolean,
topLevelKeyAllowlist: string[]|null,
maxBytes: number, // total source byte cap
maxDepth: number, // recursion depth cap
maxKeysPerObject: number, // breadth cap per object
maxArrayLength: number, // array length cap
maxStringLength: number, // string length cap
maxTotalNodes: number, // total node count cap
}
Inspect input (string of JSON source) for the full guard-json threat catalog without committing to a parsed value. Returns { ok, issues } where issues is the aggregated detector output — every prototype-pollution key, depth/breadth cap hit, duplicate-key smuggle, JSON5-quirk match, BOM placement, unicode threat, and numeric-precision-loss candidate is reported with kind / severity / ruleId / snippet. Profile-driven (strict / balanced / permissive) and posture-driven (hipaa / pci-dss / gdpr / soc2).
Detection runs in two passes: a raw-source scan (BOM placement, comments, NaN/Infinity, trailing commas, JSON5 quirks, source- level prototype-pollution keys, codepoint-class threats) followed by a parsed-tree walk (depth / breadth / array-length / string- length / node-count caps, duplicate-key rescan).
var rv = b.guardJson.validate('{"__proto__":{"polluted":true}}', {
profile: "strict",
});
rv.ok; // → false
rv.issues.some(function (i) { return i.kind === "prototype-pollution-key"; }); // → true
b.guardJson.parse(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
pollutionPolicy: "reject"|"strip"|"audit"|"allow",
bomPolicy: "reject"|"strip"|"allow",
controlPolicy: "reject"|"strip"|"allow",
zeroWidthPolicy: "reject"|"strip"|"audit"|"allow",
maxBytes: number, maxDepth: number,
}
Parse input (string of JSON source) into a JavaScript value after the guard-json threat catalog clears. Refuses on prototype- pollution keys when pollutionPolicy === "reject", refuses on any critical raw-source pre-parse threat, refuses on parse failure, and otherwise routes through b.safeJson.parse with the configured maxBytes / maxDepth caps. Strip policies (bomPolicy: "strip", controlPolicy: "strip", zeroWidthPolicy: "strip") silently remove the offending characters from the source before parsing.
Pollution keys (__proto__ / constructor / prototype) are normally invisible to Object.keys() after JSON.parse because they route through prototype setters; the parse path passes allowProto: true to b.safeJson.parse only when policy is audit / allow, ensuring strip / reject paths produce a tree with no pollution-key residue.
Throws GuardJsonError on refusal — the error code matches the triggering rule (json.prototype-pollution, json.parse, etc.).
var safe = b.guardJson.parse('{"name":"alice","age":30}', {
profile: "strict",
});
safe.name; // → "alice"
safe.age; // → 30
b.guardJson.gate(opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
name: string, // gate identity for audit / observability
}
Build a b.gateContract gate suitable for plugging into b.staticServe({ contentSafety: { ".json": gate } }), b.fileUpload({ contentSafety: { "application/json": gate } }), or any host primitive that consumes the gate-contract shape. Action chain on validation: serve (no issues) → audit-only (warn-only issues) → sanitize (high/critical but every reject- policy is off — re-parse + re-emit a cleaned tree via JSON.stringify) → refuse (critical/high under any reject policy, or sanitize threw).
Sanitize-eligibility requires every policy in the reject set (pollutionPolicy / duplicateKeyPolicy / nanInfinityPolicy / commentPolicy / trailingCommaPolicy / json5SyntaxPolicy / bomPolicy / bidiPolicy / controlPolicy / nullBytePolicy) to be off; under strict every one is "reject" so the gate jumps straight from audit-only to refuse.
var jsonGate = b.guardJson.gate({ profile: "strict" });
var hostile = Buffer.from('{"__proto__":{"x":1}}', "utf8");
var verdict = await jsonGate.check({ bytes: hostile });
verdict.action; // → "refuse"
b.guardJson.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 GuardJsonError with code "json.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.guardJson.compliancePosture("hipaa");
posture; // → overlay clone (mutable)
try {
b.guardJson.compliancePosture("not-a-regime");
} catch (e) {
e.code; // → "json.bad-posture"
}
b.guardJson.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.guardJson.buildProfile({ extends: "strict" });
custom; // → composed profile object
b.guardJson.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 GuardJsonError with code "json.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.guardJson.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id; // → "tenant-policy"
b.guardJson.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 GuardJsonError 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.guardJson.sanitize(input, { profile: "permissive" });
safe; // → normalized value
b.guardJson.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 GuardJsonError with code "json.bad-opt" / "json.bad-posture" on an unknown profile or posture name.
var resolved = b.guardJson.resolveOpts({ profile: "strict" });
resolved.profile; // → "strict"
Last updated 2026-08-08T16:39:49.652Z by seeder.