@blamejs/core 0.18.0 → 0.18.1
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/package.json +1 -1
- package/sbom.cdx.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.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.1 (2026-07-30) — **The error and adversarial paths of nine more primitives -- pagination, CloudEvents, JSONPath, structured fields, the CSV guard, async control, schema validation, incident reporting, and the S3 SigV4 signer -- are now under test.** This release adds no behaviour change. The fail-closed error paths, boundary conditions, and adversarial-input handling of b.pagination, b.cloudEvents, b.jsonPath, b.structuredFields, b.guardCsv, b.safeAsync, b.safeSchema, b.incident.report, and the object-store S3 SigV4 request signer -- previously exercised mostly on their happy paths -- are now asserted, verifying that each rejects malformed input, hostile payloads, and the edge cases the documented contract already promised. No defects were found; the primitives behaved as specified. Genuinely-unreachable defensive fallbacks (a base64 decode Node never throws on, a String method always present on the supported Node LTS, exhaustive switch defaults over a fixed token set) are documented rather than contorted into coverage. **Changed:** *Verified error-path behaviour for pagination, CloudEvents, JSONPath, structured fields, the CSV guard, async control, schema validation, incident reporting, and S3 SigV4* — b.pagination's cursor tamper/overflow/legacy-shape decode and orderBy validation; b.cloudEvents' envelope, spec-version, data-conflict, and HTTP binary-mode decode branches; b.jsonPath's filter-expression parser, function-argument literals, slice bounds, and node-cap guards; b.structuredFields' RFC 8941/9651 parse and serialize error branches; b.guardCsv's formula-injection, homoglyph, BOM, schema-validation, and serialize size-cap branches; b.safeAsync's bad-argument, abort-signal, and release-on-unheld paths across withTimeout / Mutex / Semaphore / the batching sinks; b.safeSchema's construction-time misuse throws and validation-failure arms across every builder; b.incident.report's deadline math, drop-silent persist/notify catches, and validation throws; and the S3 SigV4 signer's config validation, presigned-URL and POST-policy generation, and key-sanitization branches now have explicit tests for their failure and boundary behaviour. Behaviour is unchanged -- these assert guarantees the primitives already met -- so no migration is needed; the value is regression protection for the fail-closed paths of security-relevant primitives.
|
|
12
|
+
|
|
11
13
|
- v0.18.0 (2026-07-26) — **The mTLS CA engine moves to the zero-dependency @blamejs/pki toolkit and issues post-quantum ML-DSA-87 certificates by default; the @peculiar/x509 + pkijs bundle is removed.** b.mtlsCa's default certificate engine is rebuilt on the vendored, zero-runtime-dependency @blamejs/pki toolkit, replacing the @peculiar/x509 + pkijs meta-bundle (which is removed entirely). CA and client certificates are now issued under ML-DSA-87 (FIPS 204) by default: node:tls verifies ML-DSA certificate chains and the CertificateVerify signature on the supported Node LTS (OpenSSL 3.5), so a post-quantum client certificate completes a real mutual-auth handshake -- not just issuance. Operators whose mTLS peers are not yet on OpenSSL 3.5 pass b.mtlsCa.create({ algorithm: "ECDSA-P384-SHA384" }) for a universally-interoperable classical CA -- the pin covers both the CA and every leaf it issues, and never changes the default other CAs use; SLH-DSA is intentionally not offered for TLS. A PKCS#12 export from the PQC default carries a PBMAC1 integrity MAC (RFC 9579); a classical-bridge export uses the traditional RFC 7292 MacData so a pre-OpenSSL-3.5 peer -- the reason to choose the bridge -- can still verify it. Alongside the engine, this release closes a batch of primitive-hardening issues and adds a public PKCE generator; the vendor-currency gate now hard-fails a stale or unverifiable @blamejs/pki so the vendored copy can never lag the latest published release. **Added:** *b.auth.oauth.generatePkce -- a public PKCE (RFC 7636) generator* — b.auth.oauth.generatePkce() returns a { verifier, challenge } pair for a hand-rolled authorization-code flow that does not go through create(). The verifier is 43 base64url characters (32 CSPRNG bytes) and the code_challenge_method is always S256 (the challenge is base64url of SHA-256 over the verifier). · *b.audit.useStore redirect mode* — b.audit.useStore({ record, replaceChain: true }) routes framework audit events directly into a consumer-supplied store, skipping the b.db tamper-evident chain append. A consumer with no initialized b.db can now receive audit events (via record() and emit()/safeEmit()) instead of getting a db-not-initialized error or having events silently dropped. The default remains shadow-replication with the framework chain authoritative. **Changed:** *The default mTLS CA engine is @blamejs/pki with an ML-DSA-87 post-quantum default* — lib/mtls-engine-default.js (b.mtlsCa's default engine) is rebuilt on the vendored zero-dependency @blamejs/pki toolkit. CA and leaf certificates are issued under ML-DSA-87 by default; node:tls completes a real mutual-auth handshake with them on the supported Node LTS. Operators pin the classical bridge with b.mtlsCa.create({ algorithm: "ECDSA-P384-SHA384" }) when a peer predates OpenSSL 3.5; the pin flows into both CA generation and leaf issuance, and is per-call -- it never downgrades the default other CAs use. A pin that disagrees with an already-stored CA is refused (mtls-ca/algorithm-mismatch) -- including a stored EC CA on the wrong curve (a P-256/P-521 CA under the ECDSA-P384 pin, which every ECDSA label would otherwise satisfy by key type alone) -- rather than issuing a leaf the mismatched CA would sign into an unverifiable chain -- rotate to a fresh CA to change algorithms. A per-issuance opts.algorithm that conflicts with the CA's resolved algorithm is likewise refused (mtls-ca/algorithm-conflict) rather than issuing a leaf the pinned CA can't back for its intended peers. When no algorithm is pinned, leaves (and their PKCS#12 MAC tier) follow the stored CA's OWN algorithm under the bundled engine, not the ML-DSA-87 default -- so a deployment that upgrades with an existing classical CA and never sets algorithm keeps issuing classical leaves its established peers can verify. That inference is the bundled engine's alone: a custom b.mtlsCa.create({ engine }) resolves its own leaf algorithm from its own key and is never handed the bundled ECDSA-P384-SHA384 label (which would break a custom engine running a P-256/P-521 CA or its own label set). The classical bridge signs the CA, every leaf, and every CRL with ECDSA-with-SHA-384 (matching its label and the pre-flip release), not the toolkit's EC default of SHA-256. blamejs mtls init --algorithm ECDSA-P384-SHA384 reaches the same bridge from the CLI. Issued certificates carry a structured subject DN: the CA's common name and its OU=CAv{N} generation tag are distinct RDN attributes, and a leaf's subject is exactly CN=<cn> -- so mTLS authorization that maps the certificate CN to an identity reads the bare cn (not a doubled CN), and a policy inspecting the OU=CAv{N} generation attribute finds a real OU. A bare IPv6 SAN now encodes as an iPAddress GeneralName (it was previously mis-encoded as a DNS name). b.mtlsCa's revoke() refuses the removeFromCRL reason (RFC 5280 code 8, a delta-CRL un-revocation directive) -- accepting it would persist a code-8 entry the toolkit rejects in a full CRL, making every later generateCrl() fail and blocking all revocation publishing. A legacy code-8 entry already sitting in a registry (from a pre-release build or a hand-edited store) is dropped when a full CRL is signed -- the serial stays revoked, but the invalid reason no longer blocks publishing every other revocation. PKCS#12 packaging keeps the AES-256-CBC + PBKDF2-HMAC-SHA-512 @ 2,000,000-iteration bag protection; the outer integrity MAC follows the cert tier -- PBMAC1 (RFC 9579) for the PQC default, and the traditional RFC 7292 HMAC MacData for a classical-bridge (ECDSA-P384) export so a peer predating OpenSSL 3.5 can verify the file that PBMAC1 would leave unreadable. · *The vendor-currency gate hard-fails a stale or unverifiable @blamejs/pki* — @blamejs/pki is now always-strict in the vendored-dependency currency check: a stale copy fails the gate (as before) and a registry error while checking it is also a hard failure, rather than staying advisory. The required Vendor currency CI check therefore refuses to pass unless it can prove the vendored @blamejs/pki equals the latest published release, keeping the fast-moving toolkit current on every build. **Removed:** *The vendored @peculiar/x509 + pkijs bundle* — lib/vendor/pki.cjs (the @peculiar/x509 + pkijs + reflect-metadata + ASN.1 meta-bundle) has no remaining consumer after the engine and the test fixtures moved to @blamejs/pki, and is removed along with its MANIFEST, NOTICE, and vendor tooling entries. The framework's PKI now runs entirely on the single zero-dependency @blamejs/pki toolkit. **Fixed:** *b.outbox and b.webhook quote operator-supplied table names* — Operator-supplied table names in b.outbox and b.webhook were emitted unquoted, so a reserved-word or case-sensitive operator table broke the generated SQL. Both now quote operator table names with the same allowReserved parity b.db.from() already provides. On PostgreSQL a bare mixed-case name is folded to lowercase before quoting, so a deployment upgrading from an unquoted-name build keeps resolving to the same folded table PostgreSQL already created (a mixed-case table config does not strand its existing rows in a new case-sensitive table). · *b.selfUpdate.rollback replaces a running Windows executable and honors a maxBytes override* — b.selfUpdate.rollback could not replace a locked/running Windows image (unlike swap) and had no way to raise the 64 MiB copy cap for a large binary. rollback now moves the outgoing file aside via a rename before restoring the backup, and accepts a maxBytes override. If the restore copy then fails (over maxBytes, unreadable source, write error), the moved-aside image is put back over the target so a failed rollback never leaves the target absent -- no next-launch outage; and a backupTo that aliases the reserved quarantine path -- including via a symlink or a symlinked parent dir (resolved with realpath), or by letter case on a case-insensitive volume -- is refused before any file is touched (it would otherwise delete the backup and restore the bad binary). Separately, selfUpdate.poll pairs a detached signature to the asset it actually signs: it recognises both the appended (asset.bin -> asset.bin.sig) and -- when the asset is the SOLE artifact with its extension-stripped stem -- the extension-replacing (asset.bin -> asset.sig) one-signature conventions, and fails closed on an ambiguous sidecar (a lone asset.sig shared by asset.bin and asset.exe) or one whose name is unrelated to the asset rather than mispairing it. **Security:** *b.safeJson.parse no longer leaks a window of input bytes in its error message* — b.safeJson.parse interpolated V8's raw SyntaxError message into the thrown error, which could echo a short window of the parsed input -- including secret bytes -- to any caller that logs the error (CWE-532). It now emits only the stable error code and the numeric position, never the input substring. · *b.redact.redactText strips underscore-joined bearer tokens in free-text messages* — The message-path redactor missed id_token / refresh_token assignments (an underscore-joined name a bare-token word boundary could not match) and vault-sealed / empty-username connection-string credentials, so a credential embedded in a log message could reach a file or SIEM sink verbatim while the structured meta path stripped it. redactText now covers those shapes -- including the JSON / quoted forms ({"refresh_token":"..."}), where the opaque value sits behind a quote the value class had excluded -- matching the meta path. · *b.guardTenantId's reserved-name check is prototype-safe* — The reserved-tenant-id lookup used a bare property access, so a tenant id equal to an Object.prototype member (a prototype key) spuriously matched a reserved name. It now uses an own-property test, and separately refuses the prototype-pollution key names (__proto__ / constructor / prototype) via the framework's shared poisoned-key guard -- a tenant id used to key a plain-object store must never be one of those. · *b.daemon detached start reports a boot-time death instead of unconditional success* — b.daemon.start in detached mode reported success even when the spawned child died at boot, leaving a stale pidfile a later daemon.stop could misread. A one-shot exit handler now reaps the stale pidfile (only when it still records that child's pid) and audits a spawn failure ONLY for a boot death -- an abnormal exit (non-zero code or a terminating signal) within a boot window while the pidfile is still the child's. A clean exit or an operator stop() is no longer mislabeled as a spawn failure. The parent keeps its event loop alive through the boot window (a ref'd timer, cleared the instant the child exits) so even a short-lived launcher that spawns the daemon and returns still observes a boot death -- child.unref() alone would let it exit first and strand the pidfile. A stop() within the window -- from the starting process or a different one (e.g. a `daemon stop` CLI) -- is not misread as a boot death: stop() publishes a short-lived `<pidFile>.stopping` marker (holding the pid it is stopping) that the boot-death handler consults, so no contradictory spawn-failed audit lands before the stop record. The window is tunable via daemon.start({ bootDeathWindowMs }) (default 5s, bounded to the setTimeout 32-bit maximum; 0 opts out for immediate fire-and-forget). The .stopping marker is read through the hardened pid-sidecar reader (symlink-refused, size-capped) and cleared on a fresh start so a stale marker cannot suppress a pid-reused child's boot death. The reap itself atomically claims the sidecar (renaming it aside) before verifying ownership, so a fast operator restart that rewrites the pidfile between the check and the removal can never make the boot-death handler delete the NEW daemon's pidfile. The same discipline covers the asynchronous spawn-error path: a no-pid launch failure throws synchronously before any pidfile is written, so its late 'error' callback no longer deletes a pidfile that a caught-and-retried start has since written.
|
|
12
14
|
|
|
13
15
|
## v0.17.x
|
package/package.json
CHANGED
package/sbom.cdx.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:df7cac69-317e-4a1f-856e-8b36d1c52dc3",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-30T08:32:29.384Z",
|
|
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.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.1",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.1",
|
|
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.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.1",
|
|
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.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.1",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|