@blamejs/core 0.18.6 → 0.18.7

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.18.x
10
10
 
11
+ - v0.18.7 (2026-08-02) — **STARTTLS idle-timeout no longer injects plaintext into the encrypted channel; broader mail-server test coverage.** A STARTTLS upgrade on the POP3 / IMAP / ManageSieve / SMTP-submission / MX servers left the pre-upgrade plain-socket idle-timeout handler armed, so a post-handshake idle wrote a plaintext timeout reply into the now-encrypted stream (the peer saw a TLS decode error / reset). The shared socket-upgrade path now strips that handler so the idle timeout is delivered encrypted. Alongside the fix, error and edge branches of b.mail.server.pop3, b.mail.server.imap, and b.mail.auth are now under test. Except for the fix, behaviour is unchanged. **Changed:** *Error- and edge-path tests for the POP3, IMAP, and mail-authentication primitives* — b.mail.server.pop3 (the STLS upgrade and post-handshake idle timeout, pipelined-command draining, AUTH / APOP / USER-PASS branches, and store-fallback and tenant-refusal shapes), b.mail.server.imap (APPEND / CATENATE literal handling, command-dispatch and guard-error branches, and the LOGIN / NOTIFY paths), and b.mail.auth (SPF macro expansion and record parsing, iprev forward-confirm, and dual-CIDR a/mx parsing) now have explicit tests for their boundary paths. **Security:** *STARTTLS idle timeout is delivered over the encrypted channel, not injected as plaintext* — The POP3, IMAP, ManageSieve, SMTP-submission, and MX servers arm a plain-socket idle timer whose handler writes a plaintext idle-timeout reply and closes the connection. After a STARTTLS upgrade the shared socket-upgrade path stripped only the plain socket's data listeners, so that plaintext handler survived the wrap and, on a post-handshake idle, injected cleartext into the now-encrypted stream — the peer observed a TLS decode error or connection reset instead of a clean encrypted timeout reply. The upgrade path now strips the plain socket's timeout listeners and disarms its idle timer, so the upgraded TLS socket owns the idle timeout and replies over the encrypted channel. **Detectors:** *STARTTLS upgrade strips the plaintext idle-timeout handler* — A source guard keeps the shared socket-upgrade path stripping the plain socket's timeout listeners alongside its data listeners, so a plaintext idle-timeout reply cannot survive into the encrypted channel.
12
+
11
13
  - v0.18.6 (2026-08-01) — **Mail SMTPUTF8 + credential-validation fixes and a static-quota fail-open fix, with broad error- and edge-path test coverage.** b.mail no longer forces SMTPUTF8 on a long-but-pure-ASCII subject or address (so such a message delivers to a peer that does not advertise SMTPUTF8), and now rejects a non-string credential/identity option at config time instead of crashing mid-send. b.static enforces its per-actor concurrency and bandwidth quotas even when a proxy blanks the client IP, closing a fail-open. Alongside the fixes, error, edge, and adversarial branches of the blamejs CLI, b.mail, b.mail.bimi, and b.static are now under test. Except for the noted fixes, behaviour is unchanged. **Changed:** *restore rollback names the resolved target in its missing-path error* — b.restoreRollback.rollback's missing-target error now names the resolved rollback path, so an operator — and the CLI's relative `--rollback` resolution — can see exactly which path was checked and found missing. · *Error- and edge-path tests for the CLI, mail sender, BIMI verifier, and static-file server* — The blamejs CLI (migrate/seed runner coded-error paths, the dev runner's --watch / --ignore / --kill-signal handling and start-rejection exit, restore inspect/apply/rollback and list-rollbacks, backup inspect/verify, mtls show-cert / status, vault status, and config-drift inspect on a tampered sidecar), b.mail (SMTPUTF8 handling, reverse-DNS forward-confirm, address and EAI-domain validation edges, the outbound SMTP-smuggling refusal, DATA dot-stuffing transparency, the send-audit recipient counts, DKIM-sign and interpret error fallbacks, the console / http / resend transports and their option defaults, guardDomain profile defaults and cc/bcc validation, and the CAN-SPAM refusal audit), b.mail.bimi's verification branches (including the empty-canonical-domain SAN-authorization backstop), and b.static's request, path-resolution, and quota-enforcement branches (including concurrency-slot release on a mid-stream client abort) now have explicit tests for their boundary paths. **Fixed:** *A long pure-ASCII subject or address no longer forces SMTPUTF8* — b.mail treated any subject or address longer than 254 characters as non-ASCII content, so an otherwise-deliverable pure-ASCII message required SMTPUTF8 (RFC 6531) and was refused with `eai-required-not-supported` whenever the receiving server did not advertise SMTPUTF8. The requirement is now decided solely by whether a field carries a non-ASCII code point, independent of length: long ASCII subjects and addresses deliver to any peer, and genuinely non-ASCII content still opts into SMTPUTF8 where the peer advertises it. · *Non-string SMTP transport options are rejected at config time* — b.mail.transports.smtp accepted a present-but-non-string user, pass, host, servername, or ehloName at build, then failed later on the wire — a numeric user, for example, reached `Buffer.from` at AUTH and threw, failing the send instead of authenticating. Such a value is now rejected at config time with `mail/smtp-misconfigured`, so a misconfiguration fails fast at boot rather than at first send. **Security:** *A blank client IP no longer disables b.static per-actor quotas* — The shared request-context reader let an empty req.ip shadow the socket-address fallback, yielding a null actor key that silently skipped b.static's per-actor concurrency and bandwidth caps (fail-open) — for example behind a proxy that blanks req.ip when it can't resolve a forwarded address. A blank req.ip now falls through to the real peer address, so the quotas are enforced. **Detectors:** *SMTPUTF8 requirement stays content-based, not length-bounded* — A source guard keeps b.mail's SMTPUTF8 decision keyed on the presence of a non-ASCII code point rather than the length-bounded ASCII validity check, so the long-ASCII-subject deliverability regression cannot return. · *SMTP field validation fails closed on a non-string value* — A source guard keeps b.mail's SMTP identity/credential field check rejecting a present non-string value at config time rather than passing it through to crash on the wire. · *A blank req.ip must not shadow the socket-address fallback* — A source guard keeps the request-context reader falling through to the socket address on an empty req.ip, so per-actor quotas that key off the resolved IP cannot be silently disabled.
12
14
 
13
15
  - v0.18.5 (2026-08-01) — **Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives.** Error, edge, and adversarial branches of b.agent.orchestrator (registry hydrate/register validation, consumer spawn and start lifecycle, cluster election cache and leader probes, drain and quiesce budgets, tenant-scope authorization, per-row reseal), the b.middleware.requireMtls request gate (no-peer-cert and fingerprint-failure refusals, allow/deny-list gates, revocation-source and onAuthenticated error paths, audit outcomes), and b.i18n's translation-tree and file-load edges are now under test. Behaviour is unchanged -- these pin guarantees the primitives already met. **Changed:** *Error- and edge-path tests for the agent orchestrator, mTLS request gate, and i18n primitives* — b.agent.orchestrator (registry hydrate/register non-object refusals, consumer spawn missing-agent/missing-queue refusals and the shard-topic naming, consumer start double-start refusal and the direct / dotted-nested / unknown consume-handler method resolution, cluster onTransition election-cache clear, elect argument validation and the election cache-hit path, currentLeader/isLeader/fencingToken error handling, drain timeout and the quiesce poll budget, tenant-scope actor-missing / no-tenant authorization, and reseal store-validation with per-cell AAD-sealed filtering), the b.middleware.requireMtls gate (no-opts default, allow/deny-list normalization and gate outcomes, the connection fallback and null-peer-cert / no-raw / fingerprint-failed refusals, deny-match refusal, the revocationSource and onAuthenticated error paths including a non-Error rejection, and audit allowed/refused outcomes), and b.i18n's translation-tree validation and file-load edge branches now have explicit tests for their error and boundary paths. Behaviour is unchanged -- the additions pin guarantees the primitives already met.
@@ -517,6 +517,13 @@ function create(opts) {
517
517
  idleTimeoutMs: idleTimeoutMs,
518
518
  clearFields: ["pendingLiteral", "pendingAuth"],
519
519
  drain: _drainBuffer,
520
+ // Post-STARTTLS idle teardown MUST be TLS-aware — upgradeSocket strips the
521
+ // plain-socket timeout handler, so without this the upgraded session would
522
+ // never idle out (RFC 5804 BYE) and could stay open indefinitely.
523
+ onTimeout: function (tlsSocket) {
524
+ _writeBye(tlsSocket, "Idle timeout");
525
+ _close(tlsSocket);
526
+ },
520
527
  onSecure: function (tlsSocket) {
521
528
  _emit("mail.server.managesieve.starttls_upgraded",
522
529
  { connectionId: state.id });
@@ -414,6 +414,15 @@ function upgradeSocket(opts) {
414
414
  // post-TLS dispatcher and execute as if they had been sent over
415
415
  // the authenticated channel.
416
416
  plainSocket.removeAllListeners("data");
417
+ // The pre-upgrade plain-socket idle timer's "timeout" listener wrote a PLAINTEXT
418
+ // idle reply and closed the connection (every line-protocol server arms
419
+ // socket.setTimeout + a "timeout" handler at connect). Left attached, it fires on
420
+ // the wrapped socket after the handshake and injects cleartext into the now-
421
+ // encrypted stream — the peer sees a TLS decode error / reset instead of the TLS-
422
+ // aware onTimeout replying encrypted. Strip the plaintext listener; the TLSSocket
423
+ // arms its own idle timer + a TLS-aware onTimeout IMMEDIATELY below (before the
424
+ // handshake), so the reply is encrypted AND the handshake window stays bounded.
425
+ plainSocket.removeAllListeners("timeout");
417
426
  // Pause so the kernel TCP buffer doesn't drain into the old
418
427
  // handler in the window before TLSSocket attaches its own.
419
428
  if (typeof plainSocket.pause === "function") {
@@ -425,14 +434,21 @@ function upgradeSocket(opts) {
425
434
  secureContext: opts.secureContext,
426
435
  });
427
436
 
437
+ // Arm the TLS-aware idle timeout NOW, before the handshake completes — a peer
438
+ // that sends STARTTLS then withholds / trickles the ClientHello must not hold the
439
+ // connection (and its tracked rate-limit slot) open indefinitely. The TLSSocket
440
+ // owns the timer for the whole post-upgrade lifetime; onTimeout closes it (its
441
+ // reply serializes encrypted once secure). Arming only on "secure" left the
442
+ // handshake interval unbounded.
443
+ if (idleTimeoutMs !== undefined && typeof tlsSocket.setTimeout === "function") {
444
+ try { tlsSocket.setTimeout(idleTimeoutMs); }
445
+ catch (_e) { /* tolerate */ }
446
+ }
447
+ if (typeof opts.onTimeout === "function") {
448
+ tlsSocket.on("timeout", function () { opts.onTimeout(tlsSocket); });
449
+ }
450
+
428
451
  tlsSocket.on("secure", function () {
429
- if (idleTimeoutMs !== undefined && typeof tlsSocket.setTimeout === "function") {
430
- try { tlsSocket.setTimeout(idleTimeoutMs); }
431
- catch (_e) { /* tolerate */ }
432
- }
433
- if (typeof opts.onTimeout === "function") {
434
- tlsSocket.on("timeout", function () { opts.onTimeout(tlsSocket); });
435
- }
436
452
  try { opts.onSecure(tlsSocket); }
437
453
  catch (e) { try { opts.onError(e); } catch (_e) { /* drop-silent */ } }
438
454
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.18.6",
3
+ "version": "0.18.7",
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:9df8167e-caa6-4201-86c2-8c81536955c7",
5
+ "serialNumber": "urn:uuid:212a25f7-e069-40b5-9c4b-b7f4658f9a8a",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-02T01:38:37.297Z",
8
+ "timestamp": "2026-08-02T04:21:19.772Z",
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.6",
22
+ "bom-ref": "@blamejs/core@0.18.7",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.18.6",
25
+ "version": "0.18.7",
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.6",
29
+ "purl": "pkg:npm/%40blamejs/core@0.18.7",
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.6",
57
+ "ref": "@blamejs/core@0.18.7",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]