@blamejs/core 0.10.5 → 0.10.6

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,7 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.10.x
10
10
 
11
+ - v0.10.6 (2026-05-17) — **Vendored-SBOM CycloneDX 1.6 conformance + cosign verification recipe pin.** Build-side + verification-side improvements; no runtime changes. **(a) `scripts/build-vendored-sbom.js` per-component `cpe` field** — every vendored bundle gets a CPE 2.3 string (`cpe:2.3:a:<vendor>:<product>:<version>:*:*:*:*:*:*:*`). CISA / NVD CVE-matching tools (Dependency-Track, OWASP Dependency-Check, Snyk SBOM Monitor) match CVE advisories against components by CPE; the prior emit had no CPE field, so vendored bundles were invisible to operator-side CVE scanners. **(b) Per-component `supplier` block** — `metadata.supplier` (framework-level) was already populated; each vendored bundle now also carries its own `components[].supplier` with the upstream maintainer / org per [SLSA v1.0 provenance requirements](https://slsa.dev/spec/v1.0/provenance) — operators auditing the SBOM see both the framework supplier (blamejs) AND the vendored bundle's upstream supplier (noble-curves, noble-ciphers, etc.) at the component level. **(c) `metadata.lifecycles[].externalReferences[]`** — CycloneDX 1.6 §4.4.2 requires `lifecycles` entries to carry build-provenance references (workflow URL, run ID); the npm-publish workflow now populates these so the SBOM points back at the SLSA-attesting workflow run that produced the tarball. **(d) Sub-component `dependsOn` graph** — when a vendored bundle exposes sub-components (e.g. `noble-ciphers` exports `xchacha20poly1305` + `aes-gcm` as named sub-modules), each sub-component now emits its own SBOM entry with a `dependencies` edge pointing to its parent (CycloneDX 1.6 §4.7). Operators get the full transitive graph instead of just the top-level vendored bundle. **(e) `_licenseFor()` inline-path fix** — the path-resolution branch that handles vendored bundles whose `package.json` is under `lib/vendor/<name>/package.json` now correctly returns the SPDX `license.id` (was returning `null` for that branch, causing CycloneDX-validator warnings). **(f) `SECURITY.md` cosign verification recipe pinned to workflow path + tag-ref** — the operator-side recipe now constrains `cosign verify-blob --certificate-identity-regexp` to the specific workflow file (`.github/workflows/npm-publish.yml`) + tag-ref shape (`refs/tags/v[0-9]+\.[0-9]+\.[0-9]+`), refusing certificates issued for any other workflow or ref class. Also documents `--rekor-url` for operators running on an air-gapped network with a local transparency log + offline TUF root path for `cosign initialize --root <local-root.json>`. **(g) `.github/workflows/npm-publish.yml` recipe comment** synchronized to match the SECURITY.md recipe so operators copy-pasting from either source see identical verification steps. **Operator impact:** SBOM consumers that previously saw vendored bundles as opaque now see CPE-matched components with proper supplier attribution + transitive sub-component graph. The Sigstore-keyless verification recipe is more restrictive (rejects certificates issued for non-`npm-publish.yml` workflows on this repo) — operators already verifying against the prior recipe see the same successful verification with the tighter identity match. References: [CycloneDX 1.6 §4](https://cyclonedx.org/docs/1.6/json/), [CPE 2.3 spec](https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf), [SLSA v1.0 provenance](https://slsa.dev/spec/v1.0/provenance), [Sigstore cosign verify-blob](https://docs.sigstore.dev/cosign/verifying/verify/), [TUF specification](https://theupdateframework.github.io/specification/latest/).
11
12
  - v0.10.5 (2026-05-16) — **`b.mail.server.pop3` APOP cleartext refusal + `b.vendorData` constant-time digest compares.** Two small entry-tier refusals on the mail and vendor-data surfaces. **(a) `b.mail.server.pop3._handleApop`** refuses APOP when the connection is cleartext and the profile is not permissive, symmetric with the existing USER / PASS refusal. APOP transmits `MD5(timestamp+secret)` (not cleartext credentials), but an attacker who captures the digest plus the known greeting timestamp can mount an offline dictionary attack against the shared secret. RFC 1939 §7 explicitly warns about this; the wire MUST be TLS-protected to deny the offline-attack vector. Emits the same `mail.server.pop3.auth_refused_cleartext` audit event + writes `-ERR APOP refused over cleartext (use STLS first; RFC 1939 §7)`. The cleartext-refusal line was advertised in the v0.10.4 release notes but the wire-level enforcement only lands here; operators relying on v0.10.4 saw the comment but not the runtime gate. **(b) `b.vendorData.verifyAll()`** boot-time digest verifies (SHA-256 layer 1, SHA3-512 layer 2, and the SLH-DSA-SHAKE-256f pubkey-fingerprint cross-check) now compare via a length-prechecked `nodeCrypto.timingSafeEqual` instead of `!==`. The framework convention is that every digest / MAC compare is constant-time regardless of whether the value is a secret — reaching for `!==` whenever a value "isn't a secret" is the smell; the convention is the gate. Uses `nodeCrypto.timingSafeEqual` directly (not `b.crypto.timingSafeEqual`) because `b.crypto` is `lazyRequire`'d to break a circular load chain and isn't available during boot-time `verifyAll()`. **Operator impact:** APOP users on plaintext POP3 (port 110) without STLS first now get `-ERR` instead of authenticating — the operator either wires STLS, switches the listener to implicit TLS (port 995), or sets `profile: "permissive"` for the deliberately-open path. `b.vendorData` consumers see no behavioral change — the timing-safe compare returns the same boolean as `!==` for length-equal inputs. References: [RFC 1939 §7](https://www.rfc-editor.org/rfc/rfc1939#section-7), [CWE-208 timing attack](https://cwe.mitre.org/data/definitions/208.html), [NIST SP 800-38B §6.3 MAC verification](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38B.pdf).
12
13
  - v0.10.4 (2026-05-16) — **Mail-protocol hardening across the four listener primitives.** Ten refusals + two new operator-visible opts spanning `b.mail.server.{mx,submission,imap,pop3}`, `b.mail.server.rateLimit`, `b.safeMime`, `b.guardListUnsubscribe`. Layered on top of the v0.10.0 mail-stack baseline; addresses residual gaps in inbound RCPT enumeration, header-count amplification, POP3 UPDATE-state commit timeouts, list-unsubscribe URI shape, and the per-listener auth-failure / connection-rate maps. **(a) `b.mail.server.rateLimit.checkRcptAdmit(ip)` + `noteRcptFailure(ip)`** — new per-IP RCPT-failure budget (default 50/min, rolling 60s window) wired into the MX + submission listeners. RFC 5321 §3.5 enumeration class: an attacker probing `RCPT TO:` to map valid recipients now trips the budget after 50 failures and gets `421` for the next minute. Operators tune via `rateLimit.create({ rcptFailuresPerMinute, rcptWindowMs })`. **(b) `b.safeMime.parse({ maxHeaderCount })`** — new opt, default 512. Bounded header-count cap prevents `From: ...\r\nSubject: ...\r\n` × 100k header-list amplification in operator pipelines that pass full RFC 5322 messages through `safeMime.parse`. Refused with `safe-mime/too-many-headers` when exceeded. **(c) `b.mail.server.pop3.create({ commitTimeoutMs })`** — new opt, default `C.TIME.seconds(30)`. POP3 UPDATE-state commit (DELE materialization) now runs under `safeAsync.withTimeout` so a hung commit can no longer pin the connection past `idleTimeoutMs`. Past the cap, the connection gets `421` and the in-flight DELE batch is rolled back (RFC 1939 §6 — UPDATE state aborts on transport failure). **(d) `b.guardListUnsubscribe.validate`** refuses empty `<>` URI lists per RFC 2369 §3.1 (the `List-Unsubscribe` header value `<>` is a smuggled-empty class that downstream mail-renderers may interpret as an active unsubscribe link to the local-origin). **(e) `b.mail.server.rateLimit` GC sweep** — the previously asymmetric `connectionTimes` Map (filled in `noteConnection`, never explicitly cleaned) now sweeps empty arrays alongside the existing `authFailureTimes` cleanup. Closes a CWE-770 unbounded-memory class for long-running mail servers seeing transient IP fan-in. **(f) `b.mail.server.imap` `_close()` writes `state.stage = "closed"`** — the drain-loop guard was previously unreachable because the close path didn't update the state machine. Operators on the older path saw `state.stage === "authenticated"` linger after socket close; the new path resolves cleanly. **(g) `b.mail.server.imap` per-line cap before `Buffer.concat`** — closes a CWE-770 unbounded-`Buffer.concat` class on the IMAP line accumulator (the cap was applied AFTER concat, so a malicious peer could send 10 GiB of unterminated tag bytes and the listener would allocate before refusing). Per-line cap now gates the concat. **(h) `b.mail.server.pop3` `_handleApop` cleartext refusal** — APOP gets the same `!state.tls && profile !== "permissive"` refusal as USER / PASS, closing the cleartext-credentials gap symmetric to the other auth verbs (RFC 1939 §7 APOP MD5 is also cleartext in transit). **(i) `b.mail.server.pop3` RETR / TOP dot-stuffing via `safeSmtp.dotStuff(buf)`** — the prior `.replace(/^\./gm, "..")` on a JS string treats bare LF as a line boundary, so bodies containing bare-LF lines starting with `.` gained spurious stuffing that the receiver's strict-CRLF parser couldn't undo. Routes through the byte-level dot-stuffer that only recognizes canonical `\r\n` (RFC 1939 §3 / RFC 5321 §4.5.2). **(j) `b.mail.store` deletion atomicity** — sealed deletion no longer leaves partial state when the in-memory delete succeeds but the disk flush fails (CWE-707 — transactional integrity). **(k) `b.mail.server.submission` cleartext-AUTH audit** — the `auth_success` audit emit captures the `mechanism` field before nulling `authPending` (was recording `null`); operators tailing the audit log now see which SASL mechanism succeeded. **(l) Codebase-patterns** — new `family-subset` entry covering the rate-limit admit-check shape across `mail-server-{imap,mx,submission}` so the contract is enforced at every listener (every primitive that opens a peer socket on a mail port must consult the rate limiter before sending the greeting). **Operator impact:** `b.mail.server.rateLimit` consumers see a new public surface (`checkRcptAdmit` / `noteRcptFailure`); existing operators who don't wire these get the framework default (50/min). `b.safeMime.parse` callers with >512-header messages now get `safe-mime/too-many-headers` — operators with bespoke headers (DMARC aggregate reports can run into hundreds of `Authentication-Results`) opt up via `maxHeaderCount: 4096` per call. POP3 operators see a new `commitTimeoutMs` opt — default applies retroactively. References: [RFC 5321 §3.5](https://www.rfc-editor.org/rfc/rfc5321#section-3.5), [RFC 5322](https://www.rfc-editor.org/rfc/rfc5322), [RFC 1939](https://www.rfc-editor.org/rfc/rfc1939), [RFC 2369 §3.1](https://www.rfc-editor.org/rfc/rfc2369#section-3.1), [CWE-770](https://cwe.mitre.org/data/definitions/770.html), [CWE-707](https://cwe.mitre.org/data/definitions/707.html).
13
14
  - v0.10.3 (2026-05-16) — **`b.crypto` hardening — three entry-tier refusals on hot paths.** **(a) `b.crypto.timingSafeEqual` rejects non-Buffer / non-string inputs** — previous `Buffer.from(String(x))` coercion let a prototype-pollution-influenced caller (an Object whose `toString` returns attacker-chosen bytes) redirect the compare through bytes unrelated to the supplied value. Now throws `TypeError` at the entry boundary; string args use explicit `Buffer.from(s, "utf8")` instead of bare coercion. **(b) `b.crypto.hashCertFingerprint` caps PEM input at 64 KiB** — the `/-----BEGIN .+? -----END/` lazy-quantifier on this hot path (mTLS bootstrap / webhook verification / peer-cert pinning) is polynomial-ReDoS-class on multi-MB attacker-controlled input ([CodeQL js/polynomial-redos](https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos/)). 64 KiB covers a P-384 cert + full chain at ~3× margin; larger inputs throw `TypeError` before the regex runs. **(c) `b.crypto.namespaceHash` refuses CR / LF in string-typed `value`** — closes a log-injection / record-separator surface where an attacker-controlled HTTP header (e.g. `Idempotency-Key`) could smuggle line-break bytes into any consumer that logs the value verbatim before hashing (debug paths, audit envelopes, derived-column shadow logs). NUL is NOT refused — multiple internal callers (`b.agent.idempotency` / `b.mail.greylist` / `b.middleware.composePipeline`) use NUL as a composite-key separator, and NUL is not a log-injection byte in any standard logger. `Buffer` / `Uint8Array` inputs remain operator-side opaque bytes by contract — `namespaceHash` digests them as raw bytes, not as text, so the control-char gate does not apply there either. **Operator impact:** any caller passing a number / Object / boolean to `b.crypto.timingSafeEqual` now throws at the entry boundary instead of silently comparing coerced bytes — the API contract was already documented as Buffer-or-string, this enforces it. PEM strings larger than 64 KiB to `b.crypto.hashCertFingerprint` now throw — operators with bespoke multi-cert bundles split the inputs before calling. `namespaceHash` callers passing strings with embedded CR / LF now throw — operators ingesting attacker-influenced text validate / strip line-break bytes at the boundary, or hash opaque bytes via `Buffer` / `Uint8Array`. References: [OWASP Log Injection](https://owasp.org/www-community/attacks/Log_Injection), [CWE-117](https://cwe.mitre.org/data/definitions/117.html), [CWE-1333 ReDoS](https://cwe.mitre.org/data/definitions/1333.html).
@@ -140,8 +140,8 @@
140
140
  "source": "https://github.com/PeculiarVentures",
141
141
  "_about": "Meta-bundle of @peculiar/x509 + pkijs + reflect-metadata + every transitive ASN.1 schema package. Used by lib/mtls-engine-default.js as the pure-JS CA + PKCS#12 engine wired into b.mtlsCa.",
142
142
  "components": {
143
- "@peculiar/x509": "https://github.com/PeculiarVentures/x509",
144
- "pkijs": "https://github.com/PeculiarVentures/PKI.js"
143
+ "@peculiar/x509": { "url": "https://github.com/PeculiarVentures/x509", "version": "1.13.0" },
144
+ "pkijs": { "url": "https://github.com/PeculiarVentures/PKI.js", "version": "3.4.0" }
145
145
  },
146
146
  "exports": [
147
147
  "x509",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.10.5",
3
+ "version": "0.10.6",
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.6",
5
- "serialNumber": "urn:uuid:b04fba78-2d19-4469-a4dc-ecdf4677187f",
5
+ "serialNumber": "urn:uuid:eeaab12e-3641-448e-8bad-29a2842f802b",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-17T16:21:47.795Z",
8
+ "timestamp": "2026-05-17T18:41:38.896Z",
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.10.5",
22
+ "bom-ref": "@blamejs/core@0.10.6",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.10.5",
25
+ "version": "0.10.6",
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.10.5",
29
+ "purl": "pkg:npm/%40blamejs/core@0.10.6",
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.10.5",
57
+ "ref": "@blamejs/core@0.10.6",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]