@blamejs/core 0.13.42 → 0.13.43

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 CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.13.x
10
10
 
11
+ - v0.13.43 (2026-05-29) — **LTS window stated consistently as 24 months, experimental primitives declared semver-exempt, and stale version references cleaned up.** Documentation and operator-facing string hygiene ahead of the 1.0 stability contract. The LTS support window is now stated as 24 months everywhere (GOVERNANCE.md and the LTS calendar previously disagreed — 24 vs 18). The LTS calendar gains an explicit clause that primitives marked experimental are exempt from the stability/LTS contract, so operators can tell at a glance which surfaces may change between minors. Several error messages and doc blocks that pinned to long-past version numbers ("lands in v0.10.9", "not supported in v0.12.7", "ships in v0.6.45+") are restated version-agnostically with their escape hatch, and the S/MIME module now points operators at the live PGP encrypt/decrypt path for confidentiality today. No API or behavior changes. **Changed:** *LTS support window is consistently 24 months* — `GOVERNANCE.md` promised a 24-month LTS window while `LTS-CALENDAR.md` and `SECURITY.md` stated 18 — a six-month contradiction in the single most load-bearing number of the support contract. All three now state 24 months of security-only patches per major. The calendar table and the supported-versions prose are aligned. · *Experimental primitives are declared exempt from the stability contract* — `LTS-CALENDAR.md` now states explicitly that primitives documented as experimental (shown as "experimental" on their wiki page, and via the `experimental` segment in namespaces like `b.jose.jwe.experimental`) are not covered by the stability contract or the LTS window — they may change signature, behavior, or wire format, or be removed, in any minor without a deprecation cycle. This lets the framework ship primitives that track in-flight standards without freezing an unsettled format, and tells operators precisely which surfaces are not yet frozen. **Fixed:** *Stale version references removed from operator-facing errors and docs* — Error messages and documentation that pinned to long-past versions are restated version-agnostically with the relevant escape hatch: ZIP64 and unsupported-compression errors in archive reading, the CMS AuthEnvelopedData / fielded-decoder notes, the mTLS CRL-engine error, the safe-archive format-detection summary (which also now correctly lists the supported zip / tar / tar.gz set rather than claiming only zip), and the AI-content IPTC-reader note. None changed behavior; they no longer read as broken promises against the published version history. · *S/MIME confidentiality deferral points to the working PGP path* — `b.mail.crypto.smime` ships sign + verify; encrypt/decrypt is deferred. The deferral note previously cited an open-ended internal condition; it now names the escape hatch directly — use `b.mail.crypto.pgp.encrypt` / `decrypt` for mail confidentiality today — and states the concrete trigger that would re-open S/MIME-specific (X.509-recipient) encryption. · *Governance doc no longer references an internal file operators cannot see* — `GOVERNANCE.md` cited a rule number in a contributor-only file that does not ship in the repository. The deprecation-policy statement is now self-contained.
12
+
11
13
  - v0.13.42 (2026-05-29) — **S/MIME trust-chain validation binds the leaf to the key that verified the signature.** When b.mail.crypto.smime.verify is given trust anchors, it validates the supplied certificate chain — but it picked the chain leaf unconditionally (the first cert) and never tied it to signerPublicKey, the key that actually verified the signature. A SignedData blob could therefore carry a validly-chained certificate for one identity while the signature was verified under an unrelated key, and the chain-validated result would imply a cert↔signer binding the code never made. Chain validation now selects the leaf as the certificate whose public key matches signerPublicKey, and refuses (signer-not-in-chain) when no certificate in the chain carries that key — so a chain-validated signature is bound to the cert it claims. **Security:** *Trust-chain leaf is bound to the verified signer key* — `smime.verify({ trustAnchorCertsPem })` validated the supplied chain starting from `chain[0]` without checking that the leaf's public key was the one that verified the signature (the operator-supplied `signerPublicKey`). A crafted `SignedData` could pair a validly-chained certificate for identity A with a signature verified under an unrelated key, and the chain-valid result would assert a binding that didn't hold. The chain walk now selects the leaf as the certificate whose public key equals `signerPublicKey` (matched against the certificate's SPKI, raw key or full-encoding form), and throws `mail-crypto/smime/signer-not-in-chain` when no certificate in `SignedData.certificates` carries that key. A certificate whose key cannot be extracted is treated as a non-match, so validation fails closed rather than trusting an unverifiable binding.
12
14
 
13
15
  - v0.13.41 (2026-05-29) — **Agent registry reads can be tenant-scoped; compliance-erasure docs clarify actor is an audit field.** The agent orchestrator's registry reads (list and lookup) gated only on the flat agent-registry:read scope, so any holder could enumerate every tenant's agents and resolve a handle to one — even though the event bus already scopes subscribe and delivery by tenant. The orchestrator now mirrors that: with the new tenantScope option enabled, list returns only the actor's own tenant's agents and lookup refuses a cross-tenant name, unless the actor holds the framework cross-tenant-admin scope. Off by default, so single-tenant deployments are unaffected. Separately, the subject-erasure docs now state explicitly that the recorded actor is an audit field, not authentication — the caller must be authorized upstream. **Added:** *Tenant-scoped agent registry reads (opts.tenantScope)* — `b.agent.orchestrator.create({ tenantScope: true })` now scopes `list` and `lookup` to the calling actor's tenant: `list` filters out agents in other tenants and `lookup` returns null for a cross-tenant name, unless the actor holds the cross-tenant-admin scope (`b.agent.tenant.CROSS_TENANT_ADMIN_SCOPE`). This closes a cross-tenant metadata-enumeration and handle-acquisition path — `agent-registry:read` alone no longer exposes other tenants' agents — and mirrors the tenant scoping the event bus enforces on subscribe and delivery. The option defaults off; existing single-tenant orchestrators behave exactly as before. The `tenantId` argument to `list` remains a convenience filter, distinct from this authorization boundary. **Changed:** *Subject-erasure docs clarify the actor is an audit field, not authentication* — `b.subject.erase` and `b.subject.eraseHard` gate the deletion on operator acknowledgements and the legal-hold registry, not on caller identity. Their documentation now states explicitly that the recorded `actor` is an audit-record field, not authentication — the caller MUST be authenticated and authorized by the route before invoking. No behavior change; this removes an implicit assumption that could otherwise be read as the primitive authorizing the call.
package/LTS-CALENDAR.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # LTS calendar
2
2
 
3
3
  `@blamejs/core` ships on a published major cadence. Each major receives
4
- **18 months of security-only patches** starting the day the next major is
4
+ **24 months of security-only patches** starting the day the next major is
5
5
  published. Feature backports are not promised.
6
6
 
7
7
  | Version | First release | Security patches through | Node minimum | KEM | Cipher | KDF | Sigs |
8
8
  |---------------|---------------|-----------------------------|---------------|----------------------|-----------------------|----------|-----------------------|
9
9
  | `v0.x` (pre-1.0) | 2026-04-25 | until v1.0 ships | 24 | ML-KEM-1024 + P-384 | XChaCha20-Poly1305 | SHAKE256 | SLH-DSA-SHAKE-256f |
10
- | `v1.x` | TBD | first release + 18 months | current LTS | ML-KEM-1024 + P-384 | XChaCha20-Poly1305 | SHAKE256 | SLH-DSA-SHAKE-256f |
10
+ | `v1.x` | TBD | first release + 24 months | current LTS | ML-KEM-1024 + P-384 | XChaCha20-Poly1305 | SHAKE256 | SLH-DSA-SHAKE-256f |
11
11
 
12
12
  ## What "security patches" means
13
13
 
@@ -27,3 +27,7 @@ The "Node minimum" column is the lowest Node major the framework supports for th
27
27
  ## Pre-1.0 caveat
28
28
 
29
29
  `v0.x` has no LTS commitment. Every release may change something operators depend on; the algorithm posture is intentionally evolving. Read [CHANGELOG.md](CHANGELOG.md) before upgrading across more than a few patches at a time. The LTS calendar takes effect at v1.0.
30
+
31
+ ## Experimental primitives are exempt
32
+
33
+ Primitives documented `@status experimental` (shown as "experimental" on each wiki page, and via the `experimental` segment in namespaces such as `b.jose.jwe.experimental`) are **not** covered by the stability contract or the LTS window. They may change signature, behavior, or wire format — or be removed — in any minor, without the deprecation cycle that stable primitives get. This applies on the LTS line too. The exemption exists so the framework can ship primitives that track in-flight standards (draft RFCs, pre-IANA codepoints, newly published W3C surfaces) without freezing an unsettled format for a major's full support window. A primitive graduates to stable by dropping the `@status experimental` marker in a release whose notes call out the graduation.
@@ -30,9 +30,10 @@
30
30
  * IPTC `digitalSourceType` PhotoMetadata reading is forward-watch —
31
31
  * the framework ships no XMP / EXIF parser yet, so operators that
32
32
  * want IPTC detection pre-parse with their tool of choice and pass
33
- * the field via `opts.ipmd`. AB-853 names C2PA as "widely adopted";
34
- * IPTC PhotoMetadata reader lands in v0.10.9 once a vendoring
35
- * decision is made.
33
+ * the field via `opts.ipmd`. AB-853 names C2PA as "widely adopted".
34
+ * A built-in IPTC PhotoMetadata reader is deferred pending a vendoring
35
+ * decision for an XMP/EXIF parser; the `opts.ipmd` escape hatch covers
36
+ * the gap until then.
36
37
  *
37
38
  * @card
38
39
  * Inbound provenance detector — composes C2PA verify + CAC implicit-label parser + operator-supplied IPTC field, returns a normalized report for AB-853 / EU AI Act Art. 50 / CAC disclosure UIs.
@@ -214,10 +214,11 @@ async function _readCentralDirectory(adapter, eocd) {
214
214
  "multi-disk archives are not supported (diskNumber=" + eocd.diskNumber + ")");
215
215
  }
216
216
  if (eocd.totalEntries === 0xffff || eocd.cdSize === 0xffffffff || eocd.cdOffset === 0xffffffff) {
217
- // ZIP64 sentinel — not supported in v0.12.7. Will land in a
218
- // follow-up patch when an operator surfaces a need.
217
+ // ZIP64 sentinel — unsupported. Archives at >4 GiB / >65535 entries
218
+ // use tar instead (the escape hatch); ZIP64 read support is deferred
219
+ // until an operator surfaces a need.
219
220
  throw new ArchiveReadError("archive-read/zip64-unsupported",
220
- "ZIP64 archives are not supported in v0.12.7 (operators at >4 GiB / >65535 entries should switch to tar — lands v0.12.8)");
221
+ "ZIP64 archives are unsupported (operators at >4 GiB / >65535 entries should switch to tar)");
221
222
  }
222
223
  if (eocd.cdSize === 0 || eocd.totalEntries === 0) {
223
224
  return [];
@@ -256,7 +257,7 @@ async function _readCentralDirectory(adapter, eocd) {
256
257
  }
257
258
  if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff || lfhOffset === 0xffffffff) {
258
259
  throw new ArchiveReadError("archive-read/zip64-unsupported",
259
- "central directory entry " + n + " carries ZIP64 sentinel sizes (not supported in v0.12.7)");
260
+ "central directory entry " + n + " carries ZIP64 sentinel sizes (unsupported use tar for >4 GiB / >65535 entries)");
260
261
  }
261
262
  // ZIP names are CP437 or UTF-8 (per FLAG_UTF8_NAME bit). Decode
262
263
  // as UTF-8 unconditionally — Codex P2 territory if operators in
@@ -425,7 +426,7 @@ async function _decompressEntry(adapter, entry, dataStart, bombPolicy) {
425
426
  }
426
427
  throw new ArchiveReadError("archive-read/unsupported-method",
427
428
  "entry " + JSON.stringify(entry.name) + " uses method=" + entry.method +
428
- " — only STORE (0) and DEFLATE (8) supported in v0.12.7");
429
+ " — only STORE (0) and DEFLATE (8) are supported");
429
430
  }
430
431
 
431
432
  // ---- Public read.zip factory ---------------------------------------------
@@ -668,8 +669,10 @@ function zip(adapter, opts) {
668
669
  // entry-type policy opts in.
669
670
  if (entry.isEncrypted && !extractOpts.allowEncrypted) {
670
671
  throw new ArchiveReadError("archive-read/encrypted-entry",
671
- "entry " + JSON.stringify(entry.name) + " is encrypted — " +
672
- "v0.12.7 does not decrypt; Flavor 1/2/3 land v0.12.10/v0.12.11");
672
+ "entry " + JSON.stringify(entry.name) + " is encrypted — this " +
673
+ "low-level reader does not decrypt; use b.safeArchive for " +
674
+ "encrypted-archive handling, or pass allowEncrypted to extract " +
675
+ "the raw entry");
673
676
  }
674
677
  var typeRefusal = _enforceEntryTypePolicy(entry, entryTypePolicy);
675
678
  if (typeRefusal) {
@@ -523,7 +523,7 @@ function _validatePolicySet(table, opts) {
523
523
  "' not in allowed factors [" + ALLOWED_FACTORS.join(",") + "]");
524
524
  }
525
525
  }
526
- // Model B (cryptographic mode) ships in v0.5.1. When enabled,
526
+ // Model B (cryptographic mode). When enabled,
527
527
  // glass-locked columns must be encrypted with `b.breakGlass.encryptCell`
528
528
  // at write time (the framework can't auto-encrypt at write because
529
529
  // policy-set may post-date existing data; operators run the migration
package/lib/cms-codec.js CHANGED
@@ -42,15 +42,16 @@
42
42
  * refuses EnvelopedData and accepts only the §5083 ContentInfo
43
43
  * OID. Cheap escape hatch: operators on such a peer compose
44
44
  * `b.asn1Der` directly to rewrap an EnvelopedData blob into an
45
- * AuthEnvelopedData ContentInfo. Lights up in v0.10.14 alongside
46
- * `b.mail.smime` sign + verify, where the on-the-wire S/MIME 4.0
47
- * content shape calls for it.
45
+ * AuthEnvelopedData ContentInfo. A built-in encode path is deferred
46
+ * until an interop case requires a peer that refuses EnvelopedData;
47
+ * the `b.asn1Der` rewrap covers the gap until then.
48
48
  * - **`b.cms.decode` parse-tree of inner SignedData / EnvelopedData**
49
- * beyond the ContentInfo wrapper. v0.10.13 returns the inner
49
+ * beyond the ContentInfo wrapper. `b.cms.decode` returns the inner
50
50
  * SEQUENCE bytes as `content` (an asn1-der node); callers that
51
- * need fielded access walk it via `b.asn1Der.readSequence`. The
52
- * fielded decoders ship alongside S/MIME verify in v0.10.14 where
53
- * they're actually consumed.
51
+ * need fielded access walk it via `b.asn1Der.readSequence`. Built-in
52
+ * fielded decoders are deferred until they're actually consumed by a
53
+ * shipping primitive; the `b.asn1Der.readSequence` walk is the escape
54
+ * hatch until then.
54
55
  *
55
56
  * Refusal posture:
56
57
  *
@@ -302,8 +303,8 @@ function encodeEnvelopedData(opts) {
302
303
  * string (e.g. `"1.2.840.113549.1.7.2"` for SignedData) and
303
304
  * `content` is the inner asn1-der node (SignedData / EnvelopedData /
304
305
  * other) — operators walk it via `b.asn1Der.readSequence`. Fielded
305
- * decoders for SignedData / EnvelopedData ship in v0.10.14 alongside
306
- * S/MIME sign+verify.
306
+ * decoders for SignedData / EnvelopedData are deferred; the
307
+ * `b.asn1Der.readSequence` walk is the escape hatch until then.
307
308
  *
308
309
  * Refuses input past `opts.maxBytes` (default 64 MiB), top-level
309
310
  * non-SEQUENCE shapes, missing OID + [0] EXPLICIT child pair.
@@ -9,8 +9,10 @@
9
9
  * @card
10
10
  * S/MIME 4.0 sign + verify (PQC-first ML-DSA / SLH-DSA signers) on
11
11
  * the b.cms substrate. RFC 8551 multipart/signed with RFC 5652
12
- * SignedData; EFAIL-class encrypt/decrypt deferred until the AAD-
13
- * binding posture lands.
12
+ * SignedData. Confidentiality (encrypt/decrypt) is deferred use
13
+ * `b.mail.crypto.pgp.encrypt`/`decrypt` today; S/MIME-specific
14
+ * (X.509-recipient) encryption re-opens when an operator surfaces a
15
+ * peer that requires it, with the EFAIL defenses below applied then.
14
16
  *
15
17
  * @intro
16
18
  * S/MIME 4.0 (RFC 8551, replacing RFC 5751) `multipart/signed;
@@ -112,10 +114,12 @@ var ALLOWED_HASHES = ["sha256", "sha384", "sha512"];
112
114
  var REFUSED_HASHES = ["md5", "sha1"]; // allow:raw-byte-literal — SHAttered / RFC 8551 §2.5
113
115
 
114
116
  // PROFILES + COMPLIANCE_POSTURES — the framework's standard cross-
115
- // primitive contract. sign() and verify() (live since v0.10.16) read
116
- // these to determine which hash + RSA-bit floors apply per operator
117
- // posture; encrypt() / decrypt() (deferred per the @intro EFAIL note)
118
- // will compose the same set when they land.
117
+ // primitive contract. sign() and verify() read these to determine which
118
+ // hash + RSA-bit floors apply per operator posture. Confidentiality
119
+ // (encrypt/decrypt) is deferred b.mail.crypto.pgp.encrypt/decrypt is the
120
+ // confidentiality path today; if S/MIME-specific X.509-recipient
121
+ // encryption is added, it composes the same set with the @intro EFAIL
122
+ // defenses applied.
119
123
  var PROFILES = ["strict", "balanced", "permissive"];
120
124
  var COMPLIANCE_POSTURES = {
121
125
  hipaa: "strict",
package/lib/mtls-ca.js CHANGED
@@ -517,8 +517,8 @@ function create(opts) {
517
517
  opts3 = opts3 || {};
518
518
  if (typeof engine.generateCrl !== "function") {
519
519
  throw new MtlsCaError("mtls-ca/engine-no-crl",
520
- "configured engine does not implement generateCrl(); the bundled " +
521
- "engine ships in v0.6.45+");
520
+ "configured engine does not implement generateCrl(); use the " +
521
+ "framework's bundled CA engine, which supports it");
522
522
  }
523
523
  var ca = await initCA();
524
524
  var revocations = _loadRevocations().revocations;
@@ -22,10 +22,11 @@
22
22
  * pipeline manually.
23
23
  *
24
24
  * Format auto-detection sniffs the first ~512 bytes for magic
25
- * signatures. v0.12.7 ships ZIP detection (LFH magic `0x04034b50`
26
- * + EOCD magic `0x06054b50`); tar / gz / ae2 / `b.crypto.encryptPacked`-
27
- * wrapped formats are flagged as `safe-archive/format-unsupported`
28
- * in this patch — tar lands v0.12.8, gz v0.12.9, encryption v0.12.10/11.
25
+ * signatures: ZIP (LFH magic `0x04034b50` + EOCD magic `0x06054b50`),
26
+ * tar (`ustar` at offset 257), gzip / tar.gz (RFC 1952 magic), and
27
+ * `b.crypto.encryptPacked`-wrapped envelopes (auto-unwrapped before
28
+ * format detection). Unrecognized inputs are flagged
29
+ * `safe-archive/format-unsupported`.
29
30
  *
30
31
  * The orchestrator refuses the WHOLE archive on any single critical
31
32
  * guard issue — no partial extraction. Cleanup is `fs.rm`-recursive
@@ -148,7 +149,7 @@ async function _collectSourceBytes(source) {
148
149
  * @opts
149
150
  * source: b.archive.adapters.* | Buffer | string,
150
151
  * destination: string (target directory; created if missing),
151
- * format: "auto" | "zip" (v0.12.7 tar v0.12.8, gz v0.12.9),
152
+ * format: "auto" | "zip" | "tar" | "tar.gz",
152
153
  * bombPolicy: b.guardArchive.zipBombPolicy(...) | { ... },
153
154
  * entryTypePolicy: b.guardArchive.entryTypePolicy(...) | { ... },
154
155
  * guardProfile: "strict" | "balanced" | "permissive" | "hipaa" | ...,
@@ -275,8 +276,8 @@ async function extract(opts) {
275
276
  });
276
277
  } else {
277
278
  throw new SafeArchiveError("safe-archive/format-unsupported",
278
- "extract: format=" + JSON.stringify(format) + " — v0.12.9 ships ZIP + tar + tar.gz; " +
279
- "v0.12.10/v0.12.11 added wrap-recipient + wrap-passphrase envelopes (auto-unwrap as of v0.12.15)");
279
+ "extract: format=" + JSON.stringify(format) + " — supported formats are " +
280
+ "zip, tar, tar.gz; b.crypto.encryptPacked-wrapped archives are auto-unwrapped first");
280
281
  }
281
282
  var result = await reader.extract({
282
283
  destination: opts.destination,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.13.42",
3
+ "version": "0.13.43",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:57bde5fd-6321-44f5-9f42-0bb7c66aa56f",
5
+ "serialNumber": "urn:uuid:d368c000-a7b7-493e-bb55-a28c08371f7e",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-29T19:13:48.640Z",
8
+ "timestamp": "2026-05-30T00:14:53.721Z",
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.13.42",
22
+ "bom-ref": "@blamejs/core@0.13.43",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.13.42",
25
+ "version": "0.13.43",
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.13.42",
29
+ "purl": "pkg:npm/%40blamejs/core@0.13.43",
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.13.42",
57
+ "ref": "@blamejs/core@0.13.43",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]