Guard Domain

Domain-name identifier-safety primitive (KIND="identifier"). Validates user-supplied DNS names destined for allowlists, redirect targets, webhook endpoints, email-domain extraction, and CORS origin checks. Consumes ctx.identifier (or ctx.domain).

IDN homograph defense: mixed-script confusables (RFC 5891-5894 IDNA2008, UTS #39) — Cyrillic / Greek / Cherokee letters mixed with Latin in a single label spoof trusted domains. Strict refuses; balanced/permissive audit. The script-allowlist is operator-tunable via opts.allowedScripts. Punycode A-labels (xn--) audit by default at balanced; bare xn-- always refuses.

Label-length caps per RFC 1035 §2.3.4: 63 octets per label, 253 octets per FQDN. UTF-8 byte counting (not codepoint count) — the wire-form bound is what DNS resolvers enforce. RFC 952 / 1123 LDH grammar enforced for ASCII labels; double-hyphen at positions 3-4 without xn-- prefix audits.

TLD allowlist + public-suffix awareness: RFC 6761 special-use suffixes (.localhost / .local / .invalid / .test / .onion / .alt / .home.arpa / .internal) refuse under strict — letting these through as user-input webhook targets routes traffic to loopback / mDNS / Tor / LAN. IPv4-as-domain (dotted-decimal, octal, hex, long-decimal) and IPv6 bracket literals refuse (CVE-2021-22931 DNS-rebinding class). Single-label / TLD-only refuses under strict (search-domain suffix on misconfigured stubs).

Public-suffix and full UTS #46 ToASCII / ToUnicode round-trip ship behind operator-supplied callbacks (opts.publicSuffixList, opts.idnToAscii) — defer-with-condition until an operator surfaces a cookie-scope or email-domain canonicalization use case that needs framework-vendored tables.

BIDI / control / null-byte / zero-width are universal-refuse at every profile (CVE-2021-42574 Trojan Source class). DGA heuristic (Shannon entropy >= 3.8 bits/char on labels >= 12 chars) audits under balanced, refuses under strict.

Profiles: strict / balanced / permissive. Compliance postures: hipaa / pci-dss / gdpr / soc2.

b.guardDomain.validate(input, opts?) #

stable0.7.41hipaapci-dssgdprsoc2
{
  profile:    "strict"|"balanced"|"permissive",
  compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
  ldhPolicy:           "reject"|"audit"|"allow",
  punycodePolicy:      "reject"|"audit"|"allow",
  mixedScriptPolicy:   "reject"|"audit"|"allow",
  specialUsePolicy:    "reject"|"audit"|"allow",
  ipLiteralPolicy:     "reject"|"audit"|"allow",
  wildcardPolicy:      "reject"|"audit"|"allow",
  singleLabelPolicy:   "reject"|"audit"|"allow",
  underscorePolicy:    "reject"|"audit"|"allow",
  dgaPolicy:           "reject"|"audit"|"allow",
  trailingDotPolicy:   "normalize"|"audit"|"reject",
  allowedScripts:      string[]|null,
  dgaEntropyThreshold: number,
  dgaMinLabelLen:      number,
  maxLabelOctets:      number,    // default 63 (RFC 1035 §2.3.4)
  maxDomainOctets:     number,    // default 253 (RFC 1035 §2.3.4)
  maxBytes:            number,    // total input byte cap
}

Inspect a domain-name string and return { ok, issues }. Each issue carries { kind, severity, ruleId, snippet } with severity in "warn"|"high"|"critical". Detected: domain/label length cap (RFC 1035 §2.3.4), LDH violation, IDN A-label malformation, mixed-script homograph, special-use suffix (RFC 6761), IPv4-as-domain (every parser-permissive form), IPv6 bracket-literal, single-label / TLD-only, wildcard label, underscore label, trailing dot, DGA-shape entropy, BIDI / control / null-byte / zero-width codepoints. Pure inspection.

var rv = b.guardDomain.validate("192.168.1.1", { profile: "strict" });
rv.ok;                                             // → false
rv.issues.some(function (i) { return i.kind === "ipv4-as-domain"; });   // → true

var ok = b.guardDomain.validate("example.com", { profile: "strict" });
ok.ok;                                             // → true

b.guardDomain.sanitize(input, opts?) #

stable0.7.41
{
  profile:    "strict"|"balanced"|"permissive",
  compliancePosture: "hipaa"|"pci-dss"|"gdpr"|"soc2",
}

Normalize a domain-name string when no critical/high issues fire. Throws GuardDomainError on any high/critical refusal (homograph mix, IPv4-as-domain, special-use suffix, BIDI, malformed Punycode). Safe transforms applied otherwise: ASCII lowercasing, trailing-dot strip. Refuses to canonicalize Unicode labels — operators wanting IDN ToASCII supply opts.idnToAscii so the framework doesn't silently rewrite a label the operator's allowlist would treat as different.

var safe = b.guardDomain.sanitize("Example.Com.", { profile: "balanced" });
safe;                                              // → "example.com"

b.guardDomain.compliancePosture(name) #

stable0.7.41hipaapci-dssgdprsoc2

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 GuardDomainError with code "domain.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.guardDomain.compliancePosture("hipaa");
posture;                                             // → overlay clone (mutable)

try {
  b.guardDomain.compliancePosture("not-a-regime");
} catch (e) {
  e.code;                                            // → "domain.bad-posture"
}

b.guardDomain.buildProfile(opts) #

stable0.7.41
{
  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.guardDomain.buildProfile({ extends: "strict" });
custom;                                              // → composed profile object

b.guardDomain.loadRulePack(pack) #

stable0.7.41

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 GuardDomainError with code "domain.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.guardDomain.loadRulePack({ id: "tenant-policy", rules: [] });
pack.id;                                             // → "tenant-policy"

b.guardDomain.gate(opts?) #

stable0.7.41
{
  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.guardDomain.gate({ profile: "strict" });
var decision = await gate.check({ bytes: Buffer.from("...") });
decision.action;                                     // → "serve" | "refuse" | …

b.guardDomain.resolveOpts(opts?) #

stable0.7.41
{
  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 GuardDomainError with code "domain.bad-opt" / "domain.bad-posture" on an unknown profile or posture name.

var resolved = b.guardDomain.resolveOpts({ profile: "strict" });
resolved.profile;                                    // → "strict"

Last updated 2026-08-08T16:39:49.652Z by seeder.