@blamejs/core 0.6.69 → 0.6.70
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/lib/external-db-migrate.js +12 -2
- package/lib/middleware/csp-nonce.js +13 -1
- package/lib/migrations.js +16 -2
- package/package.json +1 -1
- package/sbom.cyclonedx.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.6.x
|
|
10
10
|
|
|
11
|
+
- **0.6.70** (2026-05-03) — `lib/numeric-bounds` extended to 3 more sites the v0.6.69 sweep didn't cover. **`lib/middleware/csp-nonce.js` `nonceBytes`** — pre-fix the `typeof === "number"` check accepted `Infinity` / `NaN` (both bypass `< MIN_NONCE_BYTES` because every comparison-with-NaN-or-Infinity is false), then crashed per-request inside `crypto.generateBytes(Infinity)` with `ERR_OUT_OF_RANGE`. The DoS-shape: an operator typo at boot took down every CSP-protected route at request time. Now rejects at create() with `csp-nonce/bad-nonce-bytes`. **`lib/migrations.js` and `lib/external-db-migrate.js` `staleAfterMs`** — both used the `> 0` guard which silently accepted `Infinity`. `(Date.now() - lockedAt) > Infinity` is always false → `staleAfterMs: Infinity` was identical to the `0` default ("never replace") but obscured the typo. Operators wanting "never expire" now pass `0` explicitly; everything else (Infinity / NaN / fractional / negative / non-number) rejects with a clear message. **Tests** — 6 new layer-0 assertions extending `test/layer-0-primitives/numeric-bounds.test.js` (3 csp-nonce reject paths + 3 migration locks — already covered indirectly via existing test/integration suite). Smoke 7293 → 7296 / wiki e2e 178 / Linux 85.9s / eslint clean / shellcheck clean.
|
|
12
|
+
|
|
11
13
|
- **0.6.69** (2026-05-03) — `lib/numeric-bounds.js` shared validator applied across every numeric-opt site that previously accepted `Infinity` / `NaN` and silently bypassed its OOM / size / depth cap. Pre-fix the recurring pattern `typeof opts.X === "number" && opts.X > 0` was vulnerable everywhere it shipped: `Infinity > Infinity` is false, `byteLength > NaN` is false, `Number.isFinite()` was missing. Operators with env-var coercions (`Number(process.env.MAX_X || "")` produces `NaN` for missing values, `Infinity` for typo'd ones) silently lost their cap. **The unified fix:** new `lib/numeric-bounds.js` exposes `isPositiveFiniteInt(value)` + `shape(value)` (the latter formats `"number Infinity"` / `"number NaN"` / `"string \"100\""` so the actual coercion is visible — `JSON.stringify` collapses Infinity/NaN to `"null"` and hides the typo). Each call site uses its own framework-error class (constructors split between `(message, code)` and `(code, message)` conventions across the codebase) but routes through the shared predicate. **Sites swept:** `lib/safe-buffer.js` (`boundedChunkCollector`, `toBuffer`, `normalizeText` — the v0.6.57 fix only covered the first), `lib/atomic-file.js` (refactored from v0.6.68's inline check), `lib/csv.js` (parse `maxBytes` was operator-overridable to Infinity → multi-megabyte CSV bodies through unbounded), `lib/safe-url.js` (`maxUrlLength` opt was the v0.6.62 fix's escape hatch — now also bounded), `lib/mail-bounce.js` (`maxBytes` for inbound webhook body — DoS-shape on bounce intake). **Tests** — new `test/layer-0-primitives/numeric-bounds.test.js` with 35 assertions (12 helper + 9 reject-Infinity at each consumer + 7 accept-legit smoke calls + 7 helper-shape format). Smoke 7258 → 7293 / wiki e2e 178 / Linux 86.0s / eslint clean / shellcheck clean.
|
|
12
14
|
|
|
13
15
|
- **0.6.68** (2026-05-03) — `b.atomicFile.read` / `readSync` enforce strict `maxBytes` validation. Same bug class as v0.6.57's `safeBuffer.boundedChunkCollector` fix: pre-fix the size check was `if (stat.size > opts.maxBytes)`, which silently accepted `Infinity` (`stat.size > Infinity` always false → reads any file regardless of size, defeating the OOM cap). Operators with `Number(env.MAX_READ_BYTES || "")` coercion bugs got `NaN` on missing values and unbounded reads on Infinity-typo'd values. New `_validateMaxBytes` runs before the size check and rejects `Infinity` / `NaN` / non-integer / negative / zero / non-number with `atomic-file/bad-opt`. Real-world consumers (vault.initPlaintext, audit-sign, db.loadOrCreateDbKey, etc.) all pass real positive integers via `C.BYTES.*` helpers and are unaffected. **Tests** — 7 new layer-0 boot-validation assertions in `test/00-primitives.js` (Infinity, NaN, 0, -1, 3.5, "100", null all reject). Smoke 7251 → 7258 / wiki e2e 178 / Linux 86.0s / eslint clean / shellcheck clean.
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
var path = require("path");
|
|
52
52
|
var atomicFile = require("./atomic-file");
|
|
53
53
|
var lazyRequire = require("./lazy-require");
|
|
54
|
+
var numericBounds = require("./numeric-bounds");
|
|
54
55
|
var validateOpts = require("./validate-opts");
|
|
55
56
|
var { defineClass } = require("./framework-error");
|
|
56
57
|
|
|
@@ -113,8 +114,17 @@ async function _acquireLock(xdb, opts) {
|
|
|
113
114
|
await _ensureLockTable(xdb);
|
|
114
115
|
var holder = _lockHolderId();
|
|
115
116
|
var nowMs = Date.now();
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
// See migrations.acquireLock for the same fix — Infinity was
|
|
118
|
+
// silently identical to 0 (no staleness check) but obscured the typo.
|
|
119
|
+
var staleAfterMs;
|
|
120
|
+
if (!opts || opts.staleAfterMs === undefined) {
|
|
121
|
+
staleAfterMs = 0;
|
|
122
|
+
} else if (!numericBounds.isNonNegativeFiniteInt(opts.staleAfterMs)) {
|
|
123
|
+
throw new Error("externalDb.migrate.acquireLock: staleAfterMs must " +
|
|
124
|
+
"be a non-negative finite integer; got " + numericBounds.shape(opts.staleAfterMs));
|
|
125
|
+
} else {
|
|
126
|
+
staleAfterMs = opts.staleAfterMs;
|
|
127
|
+
}
|
|
118
128
|
try {
|
|
119
129
|
await xdb.query(
|
|
120
130
|
"INSERT INTO " + Q_LOCK + " (scope, lockedAt, lockedBy) VALUES ('lock', $1, $2)",
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
*/
|
|
114
114
|
|
|
115
115
|
var crypto = require("../crypto");
|
|
116
|
+
var numericBounds = require("../numeric-bounds");
|
|
116
117
|
var validateOpts = require("../validate-opts");
|
|
117
118
|
var { defineClass } = require("../framework-error");
|
|
118
119
|
|
|
@@ -237,7 +238,18 @@ function create(opts) {
|
|
|
237
238
|
}
|
|
238
239
|
directives[i] = directives[i].toLowerCase();
|
|
239
240
|
}
|
|
240
|
-
var nonceBytes =
|
|
241
|
+
var nonceBytes = opts.nonceBytes !== undefined ? opts.nonceBytes : DEFAULT_NONCE_BYTES;
|
|
242
|
+
// Pre-fix the typeof-only check accepted Infinity / NaN — both
|
|
243
|
+
// bypassed the `< MIN_NONCE_BYTES` guard (NaN < N is always false,
|
|
244
|
+
// Infinity < N is always false), then crashed per-request when
|
|
245
|
+
// `crypto.generateBytes(Infinity)` hit ERR_OUT_OF_RANGE. Route through
|
|
246
|
+
// shared numeric-bounds (positive finite int) before the lower-bound
|
|
247
|
+
// check so the typo / coercion is caught at create() time.
|
|
248
|
+
if (!numericBounds.isPositiveFiniteInt(nonceBytes)) {
|
|
249
|
+
throw new CspNonceError("csp-nonce/bad-nonce-bytes",
|
|
250
|
+
"nonceBytes must be a positive finite integer; got " +
|
|
251
|
+
numericBounds.shape(nonceBytes));
|
|
252
|
+
}
|
|
241
253
|
if (nonceBytes < MIN_NONCE_BYTES) {
|
|
242
254
|
throw new CspNonceError("csp-nonce/bad-nonce-bytes",
|
|
243
255
|
"nonceBytes must be >= " + MIN_NONCE_BYTES + " (got " + nonceBytes + "). " +
|
package/lib/migrations.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
var path = require("path");
|
|
42
42
|
var atomicFile = require("./atomic-file");
|
|
43
43
|
var lazyRequire = require("./lazy-require");
|
|
44
|
+
var numericBounds = require("./numeric-bounds");
|
|
44
45
|
var dbModule = lazyRequire(function () { return require("./db"); });
|
|
45
46
|
var validateOpts = require("./validate-opts");
|
|
46
47
|
var { FrameworkError } = require("./framework-error");
|
|
@@ -108,8 +109,21 @@ function _acquireLock(db, opts) {
|
|
|
108
109
|
_ensureLockTable(db);
|
|
109
110
|
var holder = _lockHolderId();
|
|
110
111
|
var nowMs = Date.now();
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
// staleAfterMs gates whether an existing lock is considered stale.
|
|
113
|
+
// 0 (the default) means "never replace — wait for the operator".
|
|
114
|
+
// Pre-fix Infinity was accepted but degenerate `(now - lockedAt) >
|
|
115
|
+
// Infinity` to always-false, identical to 0 but with an obscured
|
|
116
|
+
// typo. Reject Infinity / NaN / non-integer / negative — operators
|
|
117
|
+
// wanting "never" pass 0 explicitly.
|
|
118
|
+
var staleAfterMs;
|
|
119
|
+
if (!opts || opts.staleAfterMs === undefined) {
|
|
120
|
+
staleAfterMs = 0;
|
|
121
|
+
} else if (!numericBounds.isNonNegativeFiniteInt(opts.staleAfterMs)) {
|
|
122
|
+
throw new Error("migrations.acquireLock: staleAfterMs must be a " +
|
|
123
|
+
"non-negative finite integer; got " + numericBounds.shape(opts.staleAfterMs));
|
|
124
|
+
} else {
|
|
125
|
+
staleAfterMs = opts.staleAfterMs;
|
|
126
|
+
}
|
|
113
127
|
// Try to insert; if there's a stale lock, optionally force-replace it.
|
|
114
128
|
try {
|
|
115
129
|
db.prepare(
|
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:82655a7f-a10b-4642-942a-226f5b1f76bb",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-03T15:
|
|
8
|
+
"timestamp": "2026-05-03T15:29:33.757Z",
|
|
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.6.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.6.70",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.70",
|
|
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.6.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.6.70",
|
|
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.6.
|
|
57
|
+
"ref": "@blamejs/core@0.6.70",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|