Guard Text
General-purpose UTF-8 free-text content-safety guard — the screen for unconstrained human text (a comment, a note, a review body, a gift message, a display name) where the legitimate content is "arbitrary letters in any writing system" but the dangerous content is a hidden codepoint that renders as nothing yet changes meaning.
Unlike the format-specific members of the guard family (csv / html / svg / json / yaml / xml / markdown), this guard imposes NO grammar on its input. Cyrillic, Han, Arabic, emoji, combining marks — all pass. What it screens is the codepoint threat catalog shared across the family:
- Unicode bidi overrides (CVE-2021-42574 Trojan Source — U+202A..U+202E, U+2066..U+2069, U+200E/F, U+061C). Visible text reads one way; the logical order is reversed. - C0 control characters (minus tab / lf / cr, which are legitimate in free text) — terminal-escape and log-injection vectors. - Null bytes — truncation / C-string-boundary attacks downstream. - Zero-width / invisible formatting chars (ZWSP / ZWNJ / ZWJ / WJ / SHY / BOM) — payload-hiding and watermark channels. - Unicode Tags block (U+E0000..U+E007F) — "ASCII smuggling": an invisible copy of an ASCII instruction an LLM tokenizer reads verbatim (prompt-injection over a comment field). - Mixed-script confusables (UTS #39) — a Cyrillic letter inside an otherwise-Latin word. Audit severity by default (legitimate multilingual text mixes scripts); promoted to refuse under the strict profile and the regulated postures.
Three profiles ship — strict / balanced / permissive — plus four compliance postures (hipaa / pci-dss / gdpr / soc2). strict rejects bidi / control / null; balanced strips them and serves the cleaned text; permissive strips the invisibles and only audits the rest. Sanitize is a SHRINKING operation by contract — stripping invisible codepoints never grows the string; an amplification past sanitizeAmplificationCap (default 1.5x) is refused.
b.guardText.gate(opts) plugs into b.fileUpload / b.staticServe / b.mail / b.objectStore / b.guardAll like every other content guard.
Threat-detection regex literals are composed from the numeric codepoint tables in b.codepointClass. The source file never embeds the attack characters themselves (the family ASCII-purity invariant).
b.guardText.validate(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
bidiPolicy: "reject"|"strip"|"audit"|"allow",
controlPolicy: "reject"|"strip"|"allow",
nullBytePolicy: "reject"|"strip"|"allow",
zeroWidthPolicy: "reject"|"strip"|"allow",
tagsPolicy: "reject"|"strip"|"allow",
confusablePolicy: "reject"|"audit"|"allow",
encodingPolicy: "reject"|"audit"|"allow", // malformed UTF-8 (default reject)
asciiOnly: boolean, // keyspace = US-ASCII only (default false)
maxCodepoint: number, // keyspace ceiling (e.g. 0xFFFF for BMP-only)
allowedScripts: Array, // confusable allowlist (e.g. ["latin","han"])
maxBytes: number, // default 1 MiB, measured in UTF-8 bytes
}
Inspect input (string or Buffer of UTF-8 text) and return { ok, issues }. Each issue carries { kind, severity, ruleId, location, snippet } with severity in "warn"|"high"|"critical". Three validation axes: (1) ENCODING — a Buffer is decoded as STRICT UTF-8, so a malformed / overlong / truncated sequence is flagged invalid-encoding rather than silently lossily decoded to U+FFFD (the overlong-encoding filter-bypass); a JS string is checked for unpaired surrogates. (2) KEYSPACE — asciiOnly pins the allowed codepoint range to US-ASCII and maxCodepoint sets a ceiling (distinct from the script axis: the raw codepoint range, not which writing systems mix). (3) CODEPOINT THREATS — Unicode bidi override (CVE-2021-42574 Trojan Source), C0 control char, null byte, zero-width / invisible char, Unicode Tags block char (ASCII smuggling), and mixed-script confusable. Arbitrary letters in any single script are NOT issues — this guard imposes no grammar. ok is false only when at least one issue is high or critical. Pure inspection — never mutates input or throws (other than the maxBytes positive-finite-integer opt check). The maxBytes limit is measured in UTF-8 BYTES. Passing Infinity for maxBytes throws.
var rv = b.guardText.validate("hello world", { profile: "strict" });
rv.ok; // → true
// Build the hostile input programmatically so the source stays ASCII.
var RLO = String.fromCharCode(0x202E);
var bad = b.guardText.validate("review " + RLO + "txt.exe", { profile: "strict" });
bad.ok; // → false
bad.issues[0].kind; // → "bidi-override"
b.guardText.sanitize(input, opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
bidiPolicy: "reject"|"strip"|"audit"|"allow",
controlPolicy: "reject"|"strip"|"allow",
nullBytePolicy: "reject"|"strip"|"allow",
zeroWidthPolicy: "reject"|"strip"|"allow",
tagsPolicy: "reject"|"strip"|"allow",
sanitizeAmplificationCap: number, // default 1.5
}
Best-effort cleanup of input (string or Buffer): strips bidi overrides (when bidiPolicy: "strip"), C0 control chars (controlPolicy: "strip"), null bytes (nullBytePolicy: "strip"), zero-width / invisible chars (zeroWidthPolicy: "strip"), and Unicode Tags block chars (tagsPolicy: "strip"). Legitimate letters in any script are preserved; a mixed-script confusable is NEVER auto-repaired (there is no safe automated repair — the gate refuses it instead). Sanitize is a SHRINKING operation by contract: when the output exceeds sanitizeAmplificationCap (default 1.5x) the function throws GuardTextError("text.sanitize-amplified").
var ZWSP = String.fromCharCode(0x200B);
var clean = b.guardText.sanitize("nice" + ZWSP + "review", { profile: "balanced" });
clean.indexOf(ZWSP) === -1; // → true
clean; // → "nicereview"
b.guardText.gate(opts?) #
{
profile: "strict"|"balanced"|"permissive",
compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
name: string, // gate identity for audit / observability
operatorRules: Array, // [{ id, severity, detect: function, reason }]
}
Build a b.gateContract gate suitable for plugging into b.fileUpload({ contentSafety: { "text/plain": gate } }), b.staticServe({ contentSafety: { ".txt": gate } }), b.mail, or b.objectStore. Action chain on inspection: serve (no issues) → audit-only (warn-only issues — e.g. a mixed-script confusable under confusablePolicy: "audit") → sanitize (critical/high but no reject policy active — strips the invisible codepoints and serves the cleaned text) → refuse (critical/high under any reject policy, a confusable under confusablePolicy: "reject", or when sanitize fails / amplifies past cap).
Operator extensibility: pass operatorRules: [{ id, severity, detect: fn(ctx)->boolean, reason }] to inject custom detectors alongside the built-in catalog. Rules run best-effort — a throwing detector is skipped (the framework cannot crash a request because an operator rule mishandled bytes).
var textGate = b.guardText.gate({ profile: "strict" });
var upload = b.fileUpload.create({ contentSafety: { "text/plain": textGate } });
var RLO = String.fromCharCode(0x202E);
var hostile = Buffer.from("ok " + RLO + "danger", "utf8");
var verdict = await textGate.check({ bytes: hostile });
verdict.action; // → "refuse"
b.guardText.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 GuardTextError with code "text.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.guardText.compliancePosture("hipaa");
posture; // → overlay clone (mutable)
try {
b.guardText.compliancePosture("not-a-regime");
} catch (e) {
e.code; // → "text.bad-posture"
}
b.guardText.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.guardText.buildProfile({ extends: "strict" });
custom; // → composed profile object
b.guardText.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 GuardTextError with code "text.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.guardText.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id; // → "tenant-policy"
b.guardText.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 GuardTextError with code "text.bad-opt" / "text.bad-posture" on an unknown profile or posture name.
var resolved = b.guardText.resolveOpts({ profile: "strict" });
resolved.profile; // → "strict"
Last updated 2026-08-08T16:39:49.652Z by seeder.