@blamejs/core 0.7.74 → 0.7.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/lib/audit.js +21 -3
- package/lib/redact.js +18 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.75** (2026-05-06) — Audit-emit redaction pipeline. `b.audit.safeEmit` now scrubs the `actor` / `reason` / `metadata` fields through `b.redact.redact()` before they hit the audit handler. Operators who pass `metadata: { reason: e.message }` from a caught error could land DB connection strings, bearer tokens, JWT compact-serialization fixtures, AWS access keys, PEM private keys, SSH keys, credit cards, and SSNs in audit rows; the redact pipeline catches the common shapes (sensitive field names + value-shape detectors) and replaces them with markers (`[REDACTED]`, `[REDACTED-CONN-STRING]`, `[REDACTED-JWT]`, `[REDACTED-PEM]`, `[REDACTED-AWS-KEY]`, `[REDACTED-CC]`, `[REDACTED-SSN]`, `[REDACTED-SEALED]`, `[REDACTED-SSH-KEY]`). The `b.redact` primitive existed in lib/ since v0.4.x but was dead code — `audit.safeEmit` previously passed metadata through unchanged. New connection-string detector matches `protocol://user:pass@host` shapes that surface in error messages from external-DB / SMTP / HTTP drivers (RFC 3986 generic syntax). Drop-silent on redact failure — the pipeline never breaks the caller's audit attempt.
|
|
12
|
+
|
|
11
13
|
- **0.7.74** (2026-05-06) — Email receive-side parity: DMARC aggregate (RUA) report parser + ARC trust evaluation + Authentication-Results header builder + TLS-RPT receive-side report parser. Closes the "framework can send mail compliantly but can't receive compliantly" gap. **`b.mail.dmarc.parseAggregateReport(xmlBytes, { contentType? })`** parses RFC 7489 §7.2 aggregate XML reports through the framework's existing `lib/parsers/safe-xml.js` (the existing security-focused XML parser handles XXE / DOCTYPE / entity-expansion defenses by default). Auto-detects gzip via magic bytes (`0x1f 0x8b`) or `Content-Type: application/gzip`. Returns `{ reportMetadata, policyPublished, records, totals }` with per-record source-IP / count / policy-evaluated dispositions / identifiers / DKIM + SPF auth results, plus aggregated `messages` / `aligned` / `notAligned` totals operators want for dashboards. Caps report size at 8 MiB and records-per-report at 10 000. **`b.mail.arc.evaluate(rfc822, { trustedSealers })`** wraps the existing `arc.verify` cryptographic chain check with the operator-side trust decision: given a passing chain, did any hop in the chain belong to a sealer the operator trusts? Returns `{ chainStatus, trusted, trustedHop, trustedDomain }` walking hops most-recent-first so the deepest trusted sealer wins. **`b.mail.authResults.emit({ authservId, results, fold? })`** builds the RFC 8601 Authentication-Results header value — operators consume per-method results from `b.mail.spf.verify` / `b.mail.dmarc.evaluate` / `b.mail.arc.verify`, hand them to `.emit`, and the framework formats the conformant header string with method-specific properties (`smtp.mailfrom`, `header.d`, `header.from`, `policy.iprev`, `policy.ip`, `policy.tls`). Refuses unknown methods / results at config-mistake time. **`b.network.smtp.tlsRpt.parseReport(body, { contentType? })`** is the receive-side counterpart to `tlsRpt.recordShape` / `tlsRpt.submit` — accepts a Buffer or string, auto-detects gzip, parses JSON, validates the RFC 8460 §4.4 required-fields shape (`organization-name`, `date-range`, `report-id`, `policies`), aggregates `total-successful-session-count` / `total-failure-session-count` across policies. Caps report size at 8 MiB and policies-per-report at 1024.
|
|
12
14
|
|
|
13
15
|
- **0.7.73** (2026-05-06) — `b.auth.aal` + `b.middleware.requireAal({ minimum })` — NIST SP 800-63-4 Authentication Assurance Level bands. **`b.auth.aal.fromMethods({ password, totp, webauthn, ... })`** combines a set of operator-asserted authenticator methods into the resulting band: `AAL1` (single factor — memorized secret OR single-factor cryptographic), `AAL2` (multi-factor — memorized secret + OTP/SMS/hardware/mTLS), `AAL3` (phishing-resistant multi-factor — WebAuthn / passkey / hardware-+-PIN). Recognized methods: `password`, `pin`, `totp`, `sms`, `webauthn`, `passkey`, `hardware`, `mtls`. **`b.middleware.requireAal({ minimum, getAal?, audit?, realm? })`** gates routes by the request's AAL band — reads `req.user.aal` by default (or operator-supplied `getAal(req)`), compares against the minimum, returns 401 with `WWW-Authenticate: AAL-StepUp realm="...", required="AAL2"` on insufficient assurance. The bespoke scheme name signals to the operator's frontend that a step-up flow should be triggered (re-prompt for TOTP / passkey) without reusing the generic `Bearer` challenge namespace. Audit emits `auth.aal.granted` / `auth.aal.denied` (drop-silent on observability sink failure). The framework leaves AMR/ACR claim emission to the operator's IdP; the new `b.auth.aal.AMR` constants object provides consistent OIDC-conformant strings for operators emitting access tokens with AAL info. Also exposes `b.auth.aal.meets(actual, required)` for ad-hoc band comparisons outside the middleware. **CI vendor-manifest gate fix**: vendor files now have an explicit `.gitattributes` `lib/vendor/** -text binary` declaration so git never rewrites line endings between Windows and Linux checkouts. The `lib/vendor/noble-ciphers.cjs` file was renormalized to LF on disk and re-hashed in `lib/vendor/MANIFEST.json`. Closes the v0.7.65–0.7.72 npm-publish.yml smoke-test failures (`vendor manifest: @noble/ciphers :: server hash matches`).
|
package/lib/audit.js
CHANGED
|
@@ -44,6 +44,7 @@ var { generateToken } = require("./crypto");
|
|
|
44
44
|
var cryptoField = require("./crypto-field");
|
|
45
45
|
var handlers = require("./handlers");
|
|
46
46
|
var { boot } = require("./log");
|
|
47
|
+
var redact = require("./redact");
|
|
47
48
|
var safeAsync = require("./safe-async");
|
|
48
49
|
var C = require("./constants");
|
|
49
50
|
var lazyRequire = require("./lazy-require");
|
|
@@ -734,13 +735,30 @@ function safeEmit(event) {
|
|
|
734
735
|
if (!event || typeof event !== "object") return;
|
|
735
736
|
if (typeof event.action !== "string") return; // can't emit without an action
|
|
736
737
|
try {
|
|
738
|
+
// Scrub credentials before they hit the audit handler. Operators
|
|
739
|
+
// who pass `metadata: { reason: e.message }` from a caught error
|
|
740
|
+
// can land DB connection strings, bearer tokens, and JWT compact-
|
|
741
|
+
// serialization fixtures in audit rows; redact.redact() catches the
|
|
742
|
+
// common shapes (sensitive field names + value-shape detectors:
|
|
743
|
+
// credit-card / JWT / PEM / AWS key / SSN / connection string) and
|
|
744
|
+
// replaces them with markers. Same pass also applies to actor +
|
|
745
|
+
// reason so the entire event surface is consistent. Drop-silent on
|
|
746
|
+
// redact failure — never break the caller's audit attempt.
|
|
747
|
+
var actor = event.actor || {};
|
|
748
|
+
var reason = event.reason || null;
|
|
749
|
+
var metadata = event.metadata || null;
|
|
750
|
+
try {
|
|
751
|
+
actor = redact.redact(actor);
|
|
752
|
+
if (reason !== null) reason = redact.redact(reason);
|
|
753
|
+
if (metadata !== null) metadata = redact.redact(metadata);
|
|
754
|
+
} catch (_e) { /* fall through with original values */ }
|
|
737
755
|
_ensureHandler().emit({
|
|
738
|
-
actor:
|
|
756
|
+
actor: actor,
|
|
739
757
|
action: event.action,
|
|
740
758
|
resource: event.resource || null,
|
|
741
759
|
outcome: event.outcome || "success",
|
|
742
|
-
reason:
|
|
743
|
-
metadata:
|
|
760
|
+
reason: reason,
|
|
761
|
+
metadata: metadata,
|
|
744
762
|
requestId: event.requestId || null,
|
|
745
763
|
});
|
|
746
764
|
} catch (_e) { /* audit best-effort — never break the caller */ }
|
package/lib/redact.js
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* redact.MARKER → '[REDACTED]'
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
var C = require("./constants");
|
|
27
|
+
|
|
26
28
|
var DEFAULT_MARKER = "[REDACTED]";
|
|
27
29
|
|
|
28
30
|
// Field names that are always redacted, regardless of value contents.
|
|
@@ -97,6 +99,22 @@ var VALUE_DETECTORS = [
|
|
|
97
99
|
test: function (v) { return typeof v === "string" && /^\d{3}-?\d{2}-?\d{4}$/.test(v); },
|
|
98
100
|
replacement: "[REDACTED-SSN]",
|
|
99
101
|
},
|
|
102
|
+
{
|
|
103
|
+
// Connection-string credential leak — matches `protocol://user:pass@host`
|
|
104
|
+
// shapes that surface in error messages / metadata.reason fields when
|
|
105
|
+
// an external-DB driver drops its connect URL into an Error.message.
|
|
106
|
+
// Replacement preserves the host part for operator triage but redacts
|
|
107
|
+
// the credentials.
|
|
108
|
+
name: "connection-string",
|
|
109
|
+
test: function (v) {
|
|
110
|
+
if (typeof v !== "string" || v.length < C.BYTES.bytes(8)) return false; // bound BEFORE regex test
|
|
111
|
+
if (v.length > C.BYTES.kib(8)) return false;
|
|
112
|
+
// user may be empty (e.g. redis://:password@host); password
|
|
113
|
+
// segment is required to flag this as a credentialed URI.
|
|
114
|
+
return /\b[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s:/?#]*:[^\s@/?#]+@/.test(v);
|
|
115
|
+
},
|
|
116
|
+
replacement: "[REDACTED-CONN-STRING]",
|
|
117
|
+
},
|
|
100
118
|
];
|
|
101
119
|
|
|
102
120
|
var sensitiveFieldsSet = new Set(SENSITIVE_FIELDS);
|
package/package.json
CHANGED
package/sbom.cyclonedx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:029bc278-cc4e-40cb-9b56-3102289527e4",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-06T04:13:02.729Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.75",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.75",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.75",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.75",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|