Redact

Operational-log redaction — regex-shape and field-name rules that strip PII / secrets out of every log payload before it reaches a file, debug sink, or external SIEM.

Two complementary signals run on every walk: a sensitive-field name set (case-insensitive substring match against keys like password, api_key, authorization, dpop, client_secret, refresh_token) and a value-shape detector chain (Luhn-validated credit-card numbers, JWS triplets, PEM / OpenSSH private-key blocks, AWS access-key prefixes, vault-sealed ciphertexts, connection-string credential leaks). Field-name hits replace the whole value with the configured marker; value-shape hits replace with a per-detector marker ([REDACTED-CC], [REDACTED-JWT]).

The redactor never mutates the input — every call returns a fresh object. The same payload commonly lands in two paths simultaneously (audit-log seals via vault; operational log redacts here) so in-place mutation would corrupt the sealed-then-archived copy.

classifyDefaults and installOutboundDlp extend the same primitive set into outbound-DLP duty: the classifier produces a verdict ("clean" / "redact" / "refuse") for a request body + headers, and the installer wraps httpClient / mail / webhook instances so refused requests fail with DlpError and redacted ones proceed with sanitized payloads. Posture presets (pci-dss / hipaa / fapi2 / soc2 / gdpr) pick a sensible default classifier.

b.redact.registerFieldRule(name, replacement?) #

0.1.0

Add a field name to the always-redact set. Match is case-insensitive substring, so registering secret also redacts appSecret / customer_secret. The replacement argument is accepted for symmetry with registerValueDetector but ignored — field-name hits always use the redactor's configured marker.

b.redact.registerFieldRule("internal_token");
var out = b.redact.redact({ internal_token: "abc-123" });
// → { internal_token: "[REDACTED]" }

b.redact.registerValueDetector(name, testFn, replacement) #

0.1.0

Register a custom value-shape detector. testFn(value) runs against every string value the redactor walks; truthy result substitutes the replacement (string or function — function receives the matched value and returns the substitution). Custom detectors run AFTER the built-in chain.

// Redact internal employee IDs (shape: EMP-NNNNNN).
b.redact.registerValueDetector("employee-id",
  function (v) { return /^EMP-\d{6}$/.test(v); },
  "[REDACTED-EMPID]");
var out = b.redact.redact({ note: "owner EMP-123456" });
// → { note: "owner EMP-123456" } — value-shape detectors only fire
//   on full-string match; in-string matches need a custom regex
//   replacement function.

b.redact.redact(value, opts?) #

0.1.0
{
  marker:     string,         // replacement marker; default "[REDACTED]"
  maxDepth:   number,         // recursion cap; default 50
  parentKey:  string | null,  // seed parent-key for top-level scalars
}

Walk value and return a NEW value with sensitive fields and sensitive-shaped strings replaced by the marker. Handles plain objects, arrays, primitives, Buffers (always replaced — never log raw binary). The original input is never mutated.

var safe = b.redact.redact({
  email:    "alice@example.com",
  password: "hunter2",
  card:     "4111 1111 1111 1111",
  note:     "see eyJabcdefghijk.eyJxyz.signature for proof",
});
// → { email: "alice@example.com",
//     password: "[REDACTED]",
//     card: "[REDACTED-CC]",
//     note: "see eyJabcdefghijk.eyJxyz.signature for proof" }

b.redact.classifyDefaults(opts) #

stable0.7.46hipaapci-dssgdprsoc2fapi2
{
  patterns:       string[],            // names from CLASSIFIER_PATTERNS
  extra:          object,              // additional { name: { detect, action, label } }
  overrideAction: "refuse" | "redact" | "audit-only",
  marker:         string,              // default "[REDACTED]"
}

Build a classifier function from a list of pattern names. The returned classify({ body, headers, url }) walks the body (object, string, or Buffer), inspects every header value, and returns { verdict, hits, redactedBody }. Verdict precedence is refuse > redact > audit-only > clean.

var classify = b.redact.classifyDefaults({
  patterns: ["pan", "ssn", "jwt", "aws-access-key"],
});
var v = classify({
  body:    { card: "4111111111111111", note: "ok" },
  headers: { authorization: "Bearer eyJabc.eyJdef.sig" },
});
// → v.verdict === "refuse"  (PAN match defaults to refuse)

b.redact.installOutboundDlp(opts) #

stable0.7.46hipaapci-dssgdprsoc2fapi2
{
  httpClient: object,             // instance with .request(opts)
  mail:       object,             // instance with .send(message)
  webhook:    object,             // signer instance with .send(input)
  classifier: function,           // override the default classifier
  posture:    string,             // "pci-dss" | "hipaa" | "fapi2" | "soc2" | "gdpr"
  onRefuse:   function,           // hook fired on refuse verdict
  onRedact:   function,           // hook fired on redact verdict
  onScan:     function,           // hook fired on every classify call
}

Install request-time interceptors on httpClient / mail / webhook instances so every outbound payload runs through a DLP classifier first. Refused requests reject with DlpError; redacted requests proceed with a sanitized body. Idempotent per primitive instance — installing twice on the same client no-ops.

var http  = b.httpClient.create({ baseUrl: "https://api.example.com" });
var mail  = b.mail.create({ host: "smtp.example.com", port: 587 });
var dlp = b.redact.installOutboundDlp({
  httpClient: http,
  mail:       mail,
  posture:    "pci-dss",
  onRefuse:   function (info) { console.warn("DLP refused", info.verdict.hits); },
});
// dlp.installed → { httpClient: true, mail: true, webhook: false }
// dlp.uninstall() restores the original .request / .send methods.

b.redact.isOutboundDlpInstalled() #

0.14.27hipaapci-dssgdprsoc2fapi2

Returns true when at least one primitive instance (httpClient / mail / webhook) currently carries an outbound-DLP interceptor. b.compliance.set reads this to decide whether to emit the one-time compliance.posture.outbound_dlp_unwired warning when a posture whose floor implies outbound DLP is pinned without any wiring. Read-only.

b.redact.isOutboundDlpInstalled();   // → false
var dlp = b.redact.installForPosture("hipaa", { httpClient: myHttp });
b.redact.isOutboundDlpInstalled();   // → true
dlp.uninstall();
b.redact.isOutboundDlpInstalled();   // → false

b.redact.installForPosture(posture, primitives) #

stable0.7.46hipaapci-dssgdprsoc2fapi2

Posture-coordinated install — picks the default classifier for posture and wraps the operator-supplied primitives.httpClient / .mail / .webhook so every outbound payload runs through it. A thin convenience over installOutboundDlp; direct callers usually want installOutboundDlp because it accepts the full hook surface.

The operator MUST call this with the primitive instances — pinning a posture via b.compliance.set does NOT auto-install outbound DLP, because the compliance coordinator holds no httpClient / mail / webhook handles. When a posture whose floor implies outbound DLP (hipaa / pci-dss / gdpr / soc2 / fapi-2.0) is pinned without this call, b.compliance.set emits a one-time compliance.posture.outbound_dlp_unwired audit warning so the gap is grep-able in the audit chain.

var dlp = b.redact.installForPosture("hipaa", {
  httpClient: myHttp,
  mail:       myMail,
  webhook:    myWebhook,
});
// → dlp.installed.httpClient === true

b.redact.redactText(str) #

stable0.17.13

Scrub credentials EMBEDDED in a free-text string in place, keeping the surrounding prose — for log messages and other operator-facing text where a secret may be interpolated mid-sentence. Unlike redact (structured, whole-value, anchored), this uses word-boundary fragment replacement so "login failed: <jwt> for bob" keeps everything but the jwt. Detects PEM blocks, JWTs, AWS access keys, vault-sealed ciphertext, URL-userinfo passwords (including empty-username forms), bearer tokens, key=secret assignments, SSN/EIN, and Luhn-valid PANs. The high-entropy api-key-shape detector is deliberately excluded (on free text it eats ordinary IDs / hashes / base64). Drop-safe: never throws (it runs on the hot-path log-emit sink); on any error it returns a fully-masked marker rather than the raw input. Every quantifier is length-capped (ReDoS backstop).

b.redact.redactText("token=AKIAIOSFODNN7EXAMPLE ok");  // → "token=[redacted] ok"

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