@blamejs/core 0.7.99 → 0.7.101
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 +4 -0
- package/lib/network-tls.js +78 -0
- package/lib/observability.js +82 -6
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.101** (2026-05-06) — `b.observability.traceContext` — W3C Trace Context (https://www.w3.org/TR/trace-context-1/) parser + builder. **`traceContext.parse(headerValue)`** consumes a `traceparent` HTTP header value (`00-<32hex traceId>-<16hex parentId>-<2hex flags>`) and returns `{ version, traceId, parentId, flags, sampled }` or null on malformed input. Enforces the §3.2.2.3 / §3.2.2.4 all-zero-rejection rule (zero trace-id and zero parent-id are explicitly forbidden by spec). **`traceContext.build({ traceId, parentId, sampled })`** produces a v1 `traceparent` header value; throws on bad input shape. **`traceContext.newTraceId()` / `newParentId()`** generate fresh randomized 128-bit / 64-bit hex strings (with the all-zero retry path the spec requires). Operators wiring distributed tracing across services use these to propagate trace IDs across an outbound HTTP call without a vendored OTel SDK — pair with the SEMCONV constants from v0.7.95 to build OTel-aligned spans on top.
|
|
12
|
+
|
|
13
|
+
- **0.7.100** (2026-05-06) — `b.network.tls.expiryMonitor({ intervalMs, windowMs, onExpiring })` — periodic CA-trust-store expiry monitor. Runs `expiringSoon(windowMs)` on a schedule; emits `network.tls.ca.expiry_check` audit event on every check (with `expiring` count + `total` CA count), `network.tls.ca.expiring` audit event when any CA falls inside the window, and the matching `network.tls.ca.expiring` observability counter. Optional `onExpiring(rows)` operator hook fires on every check that surfaces expiring CAs so operators can wire pager / Slack alerts. Audit metadata captures the expiring CA labels + the earliest `validTo` timestamp so dashboards can compute "days until first expiry" without re-querying. Returns a handle with `.stop()` for graceful shutdown. Closes the v0.7.26 OCSP/CT batch's continuous-trust-monitoring follow-up.
|
|
14
|
+
|
|
11
15
|
- **0.7.99** (2026-05-06) — `b.db.integrityCheck()` + `b.db.integrityMonitor({ intervalMs, ... })` — periodic SQLite corruption detection. **`b.db.integrityCheck()`** runs `PRAGMA integrity_check` against the live database and returns `"ok"` on a healthy database, or an array of corruption description strings on damage. Operators wire this into `/healthz` handlers or one-off CLI checks. **`b.db.integrityMonitor({ intervalMs, onCorruption })`** runs the check on a schedule (24h default), emits `system.db.integrity_ok` / `system.db.integrity_corrupt` audit events, and fires the `db.integrity_check_ok` / `db.integrity_check_corrupt` observability counters. Optional `onCorruption(issues)` operator hook fires on every corrupt-result so operators can wire pager alerts. The previous boot-time-only integrity check (added in v0.7.79) continues to run unchanged at db.init; the monitor is for long-running deployments where filesystem-level corruption can develop after boot. Returns a handle with `.stop()` for graceful shutdown.
|
|
12
16
|
|
|
13
17
|
- **0.7.98** (2026-05-06) — `b.ntpCheck.monitor({ intervalMs, ... })` — periodic clock-drift monitor that runs `checkDrift` on a schedule and emits audit + observability events on threshold crossings. Returns a handle with `.stop()` for graceful shutdown. Audit emissions: `system.ntp.checked` (every check), `system.ntp.drift_warn` (drift exceeds warn threshold), `system.ntp.drift_fatal` (drift exceeds fatal threshold), `system.ntp.unreachable` (every server in the list failed to respond). Observability event: `ntp.drift_ms` gauge on every successful check, labeled with the responding server. Optional `onDrift(result)` operator hook fires on every warning/fatal check so operators can wire pager / Slack notifications. The previous boot-time-only `b.ntpCheck.bootCheck` continues to run unchanged at db.init; the monitor is for long-running deployments where clock-drift can develop after boot (container with no RTC sync, ntpd stopped after boot, etc.). Closes the v0.7.79 audit-batch slice for continuous time integrity.
|
package/lib/network-tls.js
CHANGED
|
@@ -9,6 +9,7 @@ var C = require("./constants");
|
|
|
9
9
|
var safeBuffer = require("./safe-buffer");
|
|
10
10
|
var validateOpts = require("./validate-opts");
|
|
11
11
|
var lazyRequire = require("./lazy-require");
|
|
12
|
+
var safeAsync = require("./safe-async");
|
|
12
13
|
var { defineClass } = require("./framework-error");
|
|
13
14
|
|
|
14
15
|
var TlsTrustError = defineClass("TlsTrustError", { alwaysPermanent: true });
|
|
@@ -248,6 +249,82 @@ function expiringSoon(windowMs) {
|
|
|
248
249
|
});
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
// expiryMonitor — periodic check that emits audit + observability
|
|
253
|
+
// events when any CA in the trust store falls inside the expiry
|
|
254
|
+
// window. Returns a handle with .stop() for graceful shutdown.
|
|
255
|
+
//
|
|
256
|
+
// var mon = b.network.tls.expiryMonitor({
|
|
257
|
+
// intervalMs: C.TIME.hours(6),
|
|
258
|
+
// windowMs: C.TIME.days(30),
|
|
259
|
+
// onExpiring: function (rows) { /* operator hook — alerts */ },
|
|
260
|
+
// });
|
|
261
|
+
// ...
|
|
262
|
+
// mon.stop();
|
|
263
|
+
//
|
|
264
|
+
// Audit emissions:
|
|
265
|
+
// network.tls.ca.expiry_check — every check, reports total + expiring count
|
|
266
|
+
// network.tls.ca.expiring — when expiringSoon(windowMs) > 0
|
|
267
|
+
//
|
|
268
|
+
// Observability event: network.tls.ca.expiring counter labeled with
|
|
269
|
+
// the count.
|
|
270
|
+
function expiryMonitor(opts) {
|
|
271
|
+
opts = opts || {};
|
|
272
|
+
var intervalMs = opts.intervalMs;
|
|
273
|
+
var windowMs = opts.windowMs;
|
|
274
|
+
var auditOn = opts.audit !== false;
|
|
275
|
+
if (typeof intervalMs !== "number" || !isFinite(intervalMs) || intervalMs <= 0) {
|
|
276
|
+
throw new TlsTrustError("tls/bad-interval",
|
|
277
|
+
"tls.expiryMonitor: intervalMs must be a positive finite number");
|
|
278
|
+
}
|
|
279
|
+
if (typeof windowMs !== "number" || !isFinite(windowMs) || windowMs <= 0) {
|
|
280
|
+
throw new TlsTrustError("tls/bad-window",
|
|
281
|
+
"tls.expiryMonitor: windowMs must be a positive finite number");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function _tick() {
|
|
285
|
+
var rows;
|
|
286
|
+
try { rows = expiringSoon(windowMs); }
|
|
287
|
+
catch (_e) { return; }
|
|
288
|
+
if (auditOn) {
|
|
289
|
+
try {
|
|
290
|
+
audit().safeEmit({
|
|
291
|
+
action: "network.tls.ca.expiry_check",
|
|
292
|
+
outcome: rows.length > 0 ? "warn" : "ok",
|
|
293
|
+
metadata: { total: STATE.cas.length, expiring: rows.length, windowMs: windowMs },
|
|
294
|
+
});
|
|
295
|
+
} catch (_e) { /* drop-silent */ }
|
|
296
|
+
}
|
|
297
|
+
if (rows.length > 0) {
|
|
298
|
+
try { observability().safeEvent("network.tls.ca.expiring", rows.length, {}); }
|
|
299
|
+
catch (_e) { /* drop-silent */ }
|
|
300
|
+
if (auditOn) {
|
|
301
|
+
try {
|
|
302
|
+
audit().safeEmit({
|
|
303
|
+
action: "network.tls.ca.expiring",
|
|
304
|
+
outcome: "warn",
|
|
305
|
+
metadata: {
|
|
306
|
+
count: rows.length,
|
|
307
|
+
labels: rows.map(function (r) { return r.label; }),
|
|
308
|
+
earliestValidTo: rows.reduce(function (acc, r) {
|
|
309
|
+
var ms = r.validTo ? Date.parse(r.validTo) : Infinity;
|
|
310
|
+
return ms < acc ? ms : acc;
|
|
311
|
+
}, Infinity),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
} catch (_e) { /* drop-silent */ }
|
|
315
|
+
}
|
|
316
|
+
if (typeof opts.onExpiring === "function") {
|
|
317
|
+
try { opts.onExpiring(rows); } catch (_e) { /* operator hook */ }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
var handle = safeAsync.repeating(_tick, intervalMs, { name: "tls-expiry-monitor" });
|
|
323
|
+
return {
|
|
324
|
+
stop: function () { if (handle) { handle.stop(); handle = null; } },
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
251
328
|
function captureBaselineFingerprints() {
|
|
252
329
|
STATE.baselineFingerprints = STATE.cas.map(function (e) { return e.meta.fingerprint256; });
|
|
253
330
|
}
|
|
@@ -1557,6 +1634,7 @@ module.exports = {
|
|
|
1557
1634
|
clearAll: clearAll,
|
|
1558
1635
|
purgeExpired: purgeExpired,
|
|
1559
1636
|
expiringSoon: expiringSoon,
|
|
1637
|
+
expiryMonitor: expiryMonitor,
|
|
1560
1638
|
useSystemTrust: useSystemTrust,
|
|
1561
1639
|
isSystemTrustEnabled: isSystemTrustEnabled,
|
|
1562
1640
|
getTrustStore: getTrustStore,
|
package/lib/observability.js
CHANGED
|
@@ -312,11 +312,87 @@ var SEMCONV = Object.freeze({
|
|
|
312
312
|
K8S_DEPLOYMENT_NAME: "k8s.deployment.name",
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
+
// W3C Trace Context — parse / build the `traceparent` HTTP header
|
|
316
|
+
// per https://www.w3.org/TR/trace-context-1/. Operators wiring
|
|
317
|
+
// distributed tracing across services use these to propagate the
|
|
318
|
+
// trace ID across an outbound HTTP call without a vendored OTel SDK.
|
|
319
|
+
//
|
|
320
|
+
// Format: `<version>-<trace-id>-<parent-id>-<trace-flags>`
|
|
321
|
+
// version: 2 hex chars; "00" for v1
|
|
322
|
+
// trace-id: 32 hex chars (128 bits); MUST be all-zero-rejected
|
|
323
|
+
// parent-id: 16 hex chars (64 bits); MUST be all-zero-rejected
|
|
324
|
+
// trace-flags: 2 hex chars; bit 0 = sampled
|
|
325
|
+
var _TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
326
|
+
var _ALL_ZERO_TRACE = "00000000000000000000000000000000";
|
|
327
|
+
var _ALL_ZERO_PARENT = "0000000000000000";
|
|
328
|
+
|
|
329
|
+
var _HEX_RADIX = 16; // allow:raw-byte-literal — Number.parseInt radix
|
|
330
|
+
var _TRACE_FLAG_SAMPLED = 0x01; // W3C Trace Context §3.2.2.5 sampled bit
|
|
331
|
+
var _TRACE_ID_BYTES = 16; // allow:raw-byte-literal — W3C Trace Context §3.2.2.3 (16 bytes)
|
|
332
|
+
var _PARENT_ID_BYTES = 8; // allow:raw-byte-literal — W3C Trace Context §3.2.2.4 (8 bytes)
|
|
333
|
+
var _FLAGS_HEX_LEN = 2; // allow:raw-byte-literal — W3C Trace Context flags are 1 byte = 2 hex chars
|
|
334
|
+
|
|
335
|
+
function _parseTraceparent(headerValue) {
|
|
336
|
+
if (typeof headerValue !== "string" || headerValue.length === 0) return null;
|
|
337
|
+
var s = headerValue.trim().toLowerCase();
|
|
338
|
+
var m = s.match(_TRACEPARENT_RE);
|
|
339
|
+
if (!m) return null;
|
|
340
|
+
if (m[2] === _ALL_ZERO_TRACE) return null; // §3.2.2.3 — trace-id MUST NOT be zero
|
|
341
|
+
if (m[3] === _ALL_ZERO_PARENT) return null; // §3.2.2.4 — parent-id MUST NOT be zero
|
|
342
|
+
var flagsByte = parseInt(m[4], _HEX_RADIX);
|
|
343
|
+
return {
|
|
344
|
+
version: m[1],
|
|
345
|
+
traceId: m[2],
|
|
346
|
+
parentId: m[3],
|
|
347
|
+
flags: m[4],
|
|
348
|
+
sampled: (flagsByte & _TRACE_FLAG_SAMPLED) === _TRACE_FLAG_SAMPLED,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function _buildTraceparent(opts) {
|
|
353
|
+
if (!opts || typeof opts !== "object" || Array.isArray(opts)) {
|
|
354
|
+
throw new TypeError("traceContext.build: opts must be a plain object");
|
|
355
|
+
}
|
|
356
|
+
var traceId = opts.traceId;
|
|
357
|
+
var parentId = opts.parentId;
|
|
358
|
+
if (typeof traceId !== "string" || !/^[0-9a-f]{32}$/.test(traceId) || traceId === _ALL_ZERO_TRACE) {
|
|
359
|
+
throw new TypeError("traceContext.build: traceId must be 32 lowercase hex chars (non-zero)");
|
|
360
|
+
}
|
|
361
|
+
if (typeof parentId !== "string" || !/^[0-9a-f]{16}$/.test(parentId) || parentId === _ALL_ZERO_PARENT) {
|
|
362
|
+
throw new TypeError("traceContext.build: parentId must be 16 lowercase hex chars (non-zero)");
|
|
363
|
+
}
|
|
364
|
+
var flagsByte = (opts.sampled ? _TRACE_FLAG_SAMPLED : 0);
|
|
365
|
+
var flags = flagsByte.toString(_HEX_RADIX).padStart(_FLAGS_HEX_LEN, "0");
|
|
366
|
+
return "00-" + traceId + "-" + parentId + "-" + flags;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
var _nodeCryptoForTrace = require("crypto");
|
|
370
|
+
|
|
371
|
+
function _newTraceId() {
|
|
372
|
+
var hex = _nodeCryptoForTrace.randomBytes(_TRACE_ID_BYTES).toString("hex");
|
|
373
|
+
// Zero-trace-id is forbidden per spec; in the astronomically unlikely
|
|
374
|
+
// case rand returned all-zero, retry once.
|
|
375
|
+
return hex === _ALL_ZERO_TRACE ? _nodeCryptoForTrace.randomBytes(_TRACE_ID_BYTES).toString("hex") : hex;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function _newParentId() {
|
|
379
|
+
var hex = _nodeCryptoForTrace.randomBytes(_PARENT_ID_BYTES).toString("hex");
|
|
380
|
+
return hex === _ALL_ZERO_PARENT ? _nodeCryptoForTrace.randomBytes(_PARENT_ID_BYTES).toString("hex") : hex;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
var traceContext = {
|
|
384
|
+
parse: _parseTraceparent,
|
|
385
|
+
build: _buildTraceparent,
|
|
386
|
+
newTraceId: _newTraceId,
|
|
387
|
+
newParentId: _newParentId,
|
|
388
|
+
};
|
|
389
|
+
|
|
315
390
|
module.exports = {
|
|
316
|
-
tap:
|
|
317
|
-
event:
|
|
318
|
-
safeEvent:
|
|
319
|
-
timed:
|
|
320
|
-
setTap:
|
|
321
|
-
SEMCONV:
|
|
391
|
+
tap: tap,
|
|
392
|
+
event: event,
|
|
393
|
+
safeEvent: safeEvent,
|
|
394
|
+
timed: timed,
|
|
395
|
+
setTap: setTap,
|
|
396
|
+
SEMCONV: SEMCONV,
|
|
397
|
+
traceContext: traceContext,
|
|
322
398
|
};
|
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:a1fc9374-766f-4ee8-8a57-d02b5e754ce5",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-06T09:14
|
|
8
|
+
"timestamp": "2026-05-06T09:42:14.464Z",
|
|
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.101",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.101",
|
|
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.101",
|
|
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.101",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|