@blamejs/core 0.6.66 → 0.6.68
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/atomic-file.js +16 -0
- package/lib/audit-chain.js +8 -46
- package/lib/audit-tools.js +7 -14
- package/lib/canonical-json.js +100 -0
- package/lib/config-drift.js +8 -15
- package/lib/pagination.js +11 -34
- 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.6.x
|
|
10
10
|
|
|
11
|
+
- **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.
|
|
12
|
+
|
|
13
|
+
- **0.6.67** (2026-05-03) — canonical-JSON walker extracted to `lib/canonical-json.js` and applied to `lib/audit-tools.js` + `lib/config-drift.js`. Same bug class as v0.6.60 (pagination) and v0.6.66 (audit-chain) lived in two more sites: `audit-tools._canonicalize` (used for backup-bundle JSONL serialisation, requires byte-equivalence with audit-chain) and `config-drift._stableStringify` (used to hash framework config for post-boot tamper detection). Both still had the silent-data-loss walk — Date → `{}`, Buffer → `{"0":97,…}`, Map / Set → `{}`, BigInt throws, circular references stack-overflow. New `lib/canonical-json.js` exposes `stringify(value, opts?)` with `opts.bufferAs = "hex" | "reject"` (default `"hex"` for crypto / config / audit; `"reject"` for pagination's strict-cursor policy). All four call sites — audit-chain, audit-tools, config-drift, pagination — now route through it; the four near-identical inline walkers collapse to one. **Stored-row + bundle compatibility** preserved: existing audit rows verify identically (their `metadata` columns are JSON strings); backup bundles produced pre-v0.6.67 round-trip correctly because byte output for plain types is unchanged. **Tests** — 17 layer-0 audit-chain assertions from v0.6.66 cover the unified walker; existing pagination tests cover `bufferAs: "reject"`. Smoke 7251 / wiki e2e 178 / Linux 86.3s / eslint clean / shellcheck clean.
|
|
14
|
+
|
|
11
15
|
- **0.6.66** (2026-05-03) — `b.auditChain.canonicalize` deep-walks values and rejects non-plain types instead of silently round-tripping garbage. Same bug class as the v0.6.60 pagination canonicalize fix: pre-fix the walker did `Object.keys(row).map(...)` and JSON.stringify'd whatever fell through, which silently encoded **Map / Set / RegExp** as `{}`, **Symbol / function** as missing keys, **BigInt** as a thrown `Do not know how to serialize` mid-emit (DoS-shape on any operator routing bigint IDs into audit metadata), and **circular references** as the unwrapped JSON.stringify error message. The post-fix walker: BigInt → decimal string (no data loss for large account IDs / 64-bit counters from external-DB drivers); Date → ISO string (already worked via JSON.stringify but now explicit before the recursive walk); Map / Set / RegExp / Symbol / function reject with a clear constructor-name message; circular refs detected via WeakSet and rejected as a framework error. Walks recursively so nested structures (`{ a: [BigInt(1), BigInt(2)] }`, `{ a: { b: Uint8Array(…) } }`) all serialise correctly. **Stored-row compat** — existing audit rows verify identically because their stored `metadata` columns are already JSON strings; the change only affects EMIT-time serialisation. **Tests** — 17 new layer-0 assertions in `test/00-primitives.js`. Smoke 7234 → 7251 / wiki e2e 178 / Linux 86.1s / eslint clean / shellcheck clean.
|
|
12
16
|
|
|
13
17
|
- **0.6.65** (2026-05-03) — `b.scheduler.parseCron` rejects step values exceeding the field's range. Pre-fix `*/99999 * * * *` was silently accepted and degenerated to "minute 0 of every hour" (because the for-loop adding values stopped at the first iteration when `step > range`); an operator typing the typo got a once-per-hour schedule when they probably meant once per N minutes. Now `_parseCronField` rejects with `scheduler/invalid-cron` when `step > (range.max - range.min + 1)`. The bound is inclusive so `*/60 * * * *` (= "minute 0 of every hour" written with a redundant explicit step) still accepts. **Tests** — 4 new layer-0 assertions: `*/60` accepts, `*/61` rejects, `*/99999` rejects (the silent-degenerate case), `* */25 * * *` rejects (step > 24-hour range). Smoke 7230 → 7234 / wiki e2e 178 / Linux 86.9s / eslint clean / shellcheck clean.
|
package/lib/atomic-file.js
CHANGED
|
@@ -331,12 +331,28 @@ function readSync(filepath, opts) {
|
|
|
331
331
|
return _readSyncCore(filepath, opts);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
// maxBytes must be a positive finite integer. Pre-validation catches the
|
|
335
|
+
// `Infinity` typo / coercion case where an operator's
|
|
336
|
+
// `Number(env.MAX_READ_BYTES || "")` produced Infinity, which would
|
|
337
|
+
// otherwise make `stat.size > Infinity` always false and read any file
|
|
338
|
+
// regardless of size — defeating the OOM cap entirely. Same bug class
|
|
339
|
+
// as v0.6.57's safeBuffer.boundedChunkCollector fix.
|
|
340
|
+
function _validateMaxBytes(maxBytes) {
|
|
341
|
+
if (typeof maxBytes !== "number" || !Number.isFinite(maxBytes) ||
|
|
342
|
+
!Number.isInteger(maxBytes) || maxBytes <= 0) {
|
|
343
|
+
throw new AtomicFileError(
|
|
344
|
+
"maxBytes must be a positive finite integer; got " + JSON.stringify(maxBytes),
|
|
345
|
+
"atomic-file/bad-opt");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
334
349
|
function _readSyncCore(filepath, opts) {
|
|
335
350
|
if (!fs.existsSync(filepath)) {
|
|
336
351
|
var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
|
|
337
352
|
e.code = "ENOENT";
|
|
338
353
|
throw e;
|
|
339
354
|
}
|
|
355
|
+
_validateMaxBytes(opts.maxBytes);
|
|
340
356
|
var stat = fs.statSync(filepath);
|
|
341
357
|
if (stat.size > opts.maxBytes) {
|
|
342
358
|
throw new AtomicFileError(
|
package/lib/audit-chain.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* lib/audit-sign.js. This module owns the chain hash math only;
|
|
19
19
|
* verification is O(n) and walks every row at boot.
|
|
20
20
|
*/
|
|
21
|
+
var canonicalJson = require("./canonical-json");
|
|
21
22
|
var { sha3Hash } = require("./crypto");
|
|
22
23
|
|
|
23
24
|
// All-zero SHA3-512 (128 hex chars) — sentinel prevHash for the first row.
|
|
@@ -25,58 +26,19 @@ var ZERO_HASH = "0".repeat(128);
|
|
|
25
26
|
|
|
26
27
|
// Canonicalize a row for hashing. Excludes the hash/nonce columns themselves
|
|
27
28
|
// and any caller-specified columns. Sorted keys, JSON-encoded values; Buffer
|
|
28
|
-
// values converted to hex for stable byte serialization.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// keys (Symbol / function). BigInt converts to its decimal string —
|
|
34
|
-
// large account IDs / monotonic counters from external-DB drivers
|
|
35
|
-
// frequently land as BigInt, and crashing the audit emit on those would
|
|
36
|
-
// turn them into availability incidents. Circular refs throw a clean
|
|
37
|
-
// framework Error rather than the raw JSON.stringify message.
|
|
38
|
-
function _scrub(value, seen) {
|
|
39
|
-
if (value === null || typeof value === "undefined") return null;
|
|
40
|
-
var t = typeof value;
|
|
41
|
-
if (t === "string" || t === "boolean" || t === "number") return value;
|
|
42
|
-
if (t === "bigint") return String(value);
|
|
43
|
-
if (t === "symbol" || t === "function") {
|
|
44
|
-
throw new Error("audit-chain canonicalize: " + t + " value is not " +
|
|
45
|
-
"serialisable; convert to a string before emit");
|
|
46
|
-
}
|
|
47
|
-
if (Buffer.isBuffer(value)) return value.toString("hex");
|
|
48
|
-
if (value instanceof Uint8Array) return Buffer.from(value).toString("hex");
|
|
49
|
-
if (value instanceof Date) return value.toISOString();
|
|
50
|
-
// After the primitives + Buffer + Date, any remaining "object" must be
|
|
51
|
-
// a plain object or array. Map / Set / RegExp / class instances all
|
|
52
|
-
// reject with a clear constructor name in the error so operators see
|
|
53
|
-
// exactly which audit metadata field is the culprit.
|
|
54
|
-
if (value instanceof Map || value instanceof Set || value instanceof RegExp) {
|
|
55
|
-
throw new Error("audit-chain canonicalize: " + value.constructor.name +
|
|
56
|
-
" is not serialisable; convert to a plain primitive / array / object first");
|
|
57
|
-
}
|
|
58
|
-
seen = seen || new WeakSet();
|
|
59
|
-
if (seen.has(value)) {
|
|
60
|
-
throw new Error("audit-chain canonicalize: circular reference in audit row");
|
|
61
|
-
}
|
|
62
|
-
seen.add(value);
|
|
63
|
-
if (Array.isArray(value)) return value.map(function (v) { return _scrub(v, seen); });
|
|
64
|
-
var keys = Object.keys(value).sort();
|
|
65
|
-
var out = {};
|
|
66
|
-
for (var i = 0; i < keys.length; i++) {
|
|
67
|
-
out[keys[i]] = _scrub(value[keys[i]], seen);
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
|
|
29
|
+
// values converted to hex for stable byte serialization. Routes through
|
|
30
|
+
// the shared `lib/canonical-json` walker so the four canonicalize sites
|
|
31
|
+
// (this one, audit-tools, config-drift, pagination) share one
|
|
32
|
+
// implementation of the bug-class fix that started in v0.6.60 and
|
|
33
|
+
// completed in v0.6.67.
|
|
72
34
|
function canonicalize(row, excludeKeys) {
|
|
73
35
|
var ex = new Set(excludeKeys || []);
|
|
74
36
|
var keys = Object.keys(row).filter(function (k) { return !ex.has(k); }).sort();
|
|
75
37
|
var pairs = {};
|
|
76
38
|
for (var i = 0; i < keys.length; i++) {
|
|
77
|
-
pairs[keys[i]] =
|
|
39
|
+
pairs[keys[i]] = row[keys[i]];
|
|
78
40
|
}
|
|
79
|
-
return
|
|
41
|
+
return canonicalJson.stringify(pairs);
|
|
80
42
|
}
|
|
81
43
|
|
|
82
44
|
// Compute a row's hash given its predecessor's hash, the row's logical fields
|
package/lib/audit-tools.js
CHANGED
|
@@ -55,6 +55,7 @@ var fs = require("fs");
|
|
|
55
55
|
var path = require("path");
|
|
56
56
|
var atomicFile = require("./atomic-file");
|
|
57
57
|
var auditChain = require("./audit-chain");
|
|
58
|
+
var canonicalJson = require("./canonical-json");
|
|
58
59
|
var auditSign = require("./audit-sign");
|
|
59
60
|
var backupCrypto = require("./backup/crypto");
|
|
60
61
|
var clusterStorage = require("./cluster-storage");
|
|
@@ -116,20 +117,12 @@ function _requireOutDir(outDir, kind) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
// Canonical-JSON
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
var keys = Object.keys(value).sort();
|
|
127
|
-
var parts = [];
|
|
128
|
-
for (var i = 0; i < keys.length; i++) {
|
|
129
|
-
parts.push(JSON.stringify(keys[i]) + ":" + _canonicalize(value[keys[i]]));
|
|
130
|
-
}
|
|
131
|
-
return "{" + parts.join(",") + "}";
|
|
132
|
-
}
|
|
120
|
+
// Canonical-JSON via the shared lib/canonical-json walker — same bytes
|
|
121
|
+
// as audit-chain.canonicalize, config-drift._stableStringify, and
|
|
122
|
+
// pagination._canonicalize for the same input. Pre-v0.6.67 each site
|
|
123
|
+
// had its own copy of the walk, all carrying the same silent-loss bug
|
|
124
|
+
// for Date / Buffer / Map / Set / BigInt / circular refs.
|
|
125
|
+
function _canonicalize(value) { return canonicalJson.stringify(value); }
|
|
133
126
|
|
|
134
127
|
// Convert a single audit_log row to its on-disk-canonical JSON shape.
|
|
135
128
|
// Buffers become hex strings (matches audit-chain.canonicalize). Used
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical JSON — deterministic stringify with sorted keys at every depth.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the four near-identical implementations that grew up across
|
|
6
|
+
* `lib/audit-chain.js`, `lib/audit-tools.js`, `lib/config-drift.js`, and
|
|
7
|
+
* `lib/pagination.js`. They all walked `typeof === "object"` with
|
|
8
|
+
* `Object.keys(...).sort()` and silently round-tripped Date as `{}`,
|
|
9
|
+
* Buffer as `{"0":97,"1":98,…}`, Map / Set / RegExp as `{}`, Symbol /
|
|
10
|
+
* function as missing keys, and BigInt as a thrown
|
|
11
|
+
* `Do not know how to serialize a BigInt` mid-emit. Circular references
|
|
12
|
+
* stack-overflowed instead of producing a clean framework error.
|
|
13
|
+
*
|
|
14
|
+
* The walk:
|
|
15
|
+
*
|
|
16
|
+
* primitives + null + undefined → JSON.stringify (undefined → "null")
|
|
17
|
+
* bigint → decimal string ("123" not 123n)
|
|
18
|
+
* Date → ISO string
|
|
19
|
+
* Buffer / Uint8Array → hex (when bufferAs = "hex", default)
|
|
20
|
+
* throw (when bufferAs = "reject")
|
|
21
|
+
* Map / Set / RegExp → throw with constructor name
|
|
22
|
+
* symbol / function → throw with type name
|
|
23
|
+
* circular reference → throw via WeakSet detection
|
|
24
|
+
* plain array → recurse, preserve order
|
|
25
|
+
* plain object → recurse with sorted keys
|
|
26
|
+
*
|
|
27
|
+
* Two consumer policies on Buffer / Uint8Array are documented because
|
|
28
|
+
* the framework historically chose differently per call site:
|
|
29
|
+
*
|
|
30
|
+
* bufferAs: "hex" audit-chain / audit-tools / config-drift —
|
|
31
|
+
* binary data is legitimate (cert PEMs, key
|
|
32
|
+
* material, hash bytes); preserve as hex so the
|
|
33
|
+
* canonical output is reversible.
|
|
34
|
+
* bufferAs: "reject" pagination — cursor state is operator-supplied
|
|
35
|
+
* primitive data; binary in a cursor is almost
|
|
36
|
+
* always a bug; reject loudly.
|
|
37
|
+
*
|
|
38
|
+
* Operators don't call this directly — it's a framework-internal walker.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
function _scrub(value, seen, bufferAs) {
|
|
42
|
+
if (value === null || typeof value === "undefined") return null;
|
|
43
|
+
var t = typeof value;
|
|
44
|
+
if (t === "string" || t === "boolean" || t === "number") return value;
|
|
45
|
+
if (t === "bigint") return String(value);
|
|
46
|
+
if (t === "symbol" || t === "function") {
|
|
47
|
+
throw new Error("canonical-json: " + t + " value is not " +
|
|
48
|
+
"serialisable; convert to a string before passing in");
|
|
49
|
+
}
|
|
50
|
+
// Buffer / Uint8Array — policy-driven
|
|
51
|
+
if (Buffer.isBuffer(value)) {
|
|
52
|
+
if (bufferAs === "reject") {
|
|
53
|
+
throw new Error("canonical-json: Buffer is not serialisable in this " +
|
|
54
|
+
"context (bufferAs=reject); convert to a string or hex first");
|
|
55
|
+
}
|
|
56
|
+
return value.toString("hex");
|
|
57
|
+
}
|
|
58
|
+
if (value instanceof Uint8Array) {
|
|
59
|
+
if (bufferAs === "reject") {
|
|
60
|
+
throw new Error("canonical-json: Uint8Array is not serialisable in " +
|
|
61
|
+
"this context (bufferAs=reject); convert to a string or hex first");
|
|
62
|
+
}
|
|
63
|
+
return Buffer.from(value).toString("hex");
|
|
64
|
+
}
|
|
65
|
+
if (value instanceof Date) return value.toISOString();
|
|
66
|
+
// After primitives + Date + Buffer + Uint8Array, any remaining "object"
|
|
67
|
+
// must be a plain object or array. Map / Set / RegExp / class instances
|
|
68
|
+
// all reject so the silent-data-loss class is closed.
|
|
69
|
+
if (value instanceof Map || value instanceof Set || value instanceof RegExp) {
|
|
70
|
+
throw new Error("canonical-json: " + value.constructor.name +
|
|
71
|
+
" is not serialisable; convert to a plain primitive / array / object first");
|
|
72
|
+
}
|
|
73
|
+
seen = seen || new WeakSet();
|
|
74
|
+
if (seen.has(value)) {
|
|
75
|
+
throw new Error("canonical-json: circular reference detected");
|
|
76
|
+
}
|
|
77
|
+
seen.add(value);
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return value.map(function (v) { return _scrub(v, seen, bufferAs); });
|
|
80
|
+
}
|
|
81
|
+
var keys = Object.keys(value).sort();
|
|
82
|
+
var out = {};
|
|
83
|
+
for (var i = 0; i < keys.length; i++) {
|
|
84
|
+
out[keys[i]] = _scrub(value[keys[i]], seen, bufferAs);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Return the deterministic JSON string. opts.bufferAs picks the Buffer
|
|
90
|
+
// policy ("hex" default, "reject" for callers like pagination).
|
|
91
|
+
function stringify(value, opts) {
|
|
92
|
+
var bufferAs = (opts && opts.bufferAs) || "hex";
|
|
93
|
+
if (bufferAs !== "hex" && bufferAs !== "reject") {
|
|
94
|
+
throw new Error("canonical-json: bufferAs must be 'hex' or 'reject'; got " +
|
|
95
|
+
JSON.stringify(bufferAs));
|
|
96
|
+
}
|
|
97
|
+
return JSON.stringify(_scrub(value, null, bufferAs));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { stringify: stringify };
|
package/lib/config-drift.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
var fs = require("node:fs");
|
|
49
49
|
var path = require("node:path");
|
|
50
50
|
var auditSign = require("./audit-sign");
|
|
51
|
+
var canonicalJson = require("./canonical-json");
|
|
51
52
|
var crypto = require("./crypto");
|
|
52
53
|
var lazyRequire = require("./lazy-require");
|
|
53
54
|
var validateOpts = require("./validate-opts");
|
|
@@ -61,21 +62,13 @@ var _err = ConfigDriftError.factory;
|
|
|
61
62
|
var SIDECAR_NAME = "config-baseline.sig";
|
|
62
63
|
var SIDECAR_VERSION = 1;
|
|
63
64
|
|
|
64
|
-
// Stable JSON serialization
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
var keys = Object.keys(value).sort();
|
|
73
|
-
var pairs = [];
|
|
74
|
-
for (var i = 0; i < keys.length; i++) {
|
|
75
|
-
pairs.push(JSON.stringify(keys[i]) + ":" + _stableStringify(value[keys[i]]));
|
|
76
|
-
}
|
|
77
|
-
return "{" + pairs.join(",") + "}";
|
|
78
|
-
}
|
|
65
|
+
// Stable JSON serialization via the shared lib/canonical-json walker.
|
|
66
|
+
// Deterministic key order so the same snapshot always hashes to the same
|
|
67
|
+
// digest. Pre-v0.6.67 the in-line implementation silently lost Date /
|
|
68
|
+
// Map / Set / Buffer / BigInt content; the shared walker handles all
|
|
69
|
+
// of those + circular refs. Same bytes as audit-chain / audit-tools /
|
|
70
|
+
// pagination would produce for the same input.
|
|
71
|
+
function _stableStringify(value) { return canonicalJson.stringify(value); }
|
|
79
72
|
|
|
80
73
|
function _hashSnapshot(snapshot) {
|
|
81
74
|
return crypto.sha3Hash(_stableStringify(snapshot));
|
package/lib/pagination.js
CHANGED
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
*/
|
|
97
97
|
|
|
98
98
|
var nodeCrypto = require("node:crypto");
|
|
99
|
+
var canonicalJson = require("./canonical-json");
|
|
99
100
|
var crypto = require("./crypto");
|
|
100
101
|
var { defineClass } = require("./framework-error");
|
|
101
102
|
|
|
@@ -106,41 +107,17 @@ var TAG_BYTES = 16; // 128-bit HMAC tag truncated from SHA3-512
|
|
|
106
107
|
var DEFAULT_LIMIT = 25;
|
|
107
108
|
var DEFAULT_MAX_LIMIT = 100;
|
|
108
109
|
|
|
109
|
-
// Canonical JSON
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function _canonicalize(value, seen) {
|
|
120
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
121
|
-
if (value instanceof Date) return JSON.stringify(value.toISOString());
|
|
122
|
-
if (Buffer.isBuffer(value) || value instanceof Uint8Array ||
|
|
123
|
-
value instanceof Map || value instanceof Set ||
|
|
124
|
-
value instanceof RegExp) {
|
|
125
|
-
throw new PaginationError("pagination/bad-state",
|
|
126
|
-
"cursor state cannot contain " + value.constructor.name +
|
|
127
|
-
"; convert to a plain primitive (string / number / iso-string) first");
|
|
128
|
-
}
|
|
129
|
-
seen = seen || new WeakSet();
|
|
130
|
-
if (seen.has(value)) {
|
|
131
|
-
throw new PaginationError("pagination/bad-state",
|
|
132
|
-
"cursor state contains a circular reference");
|
|
133
|
-
}
|
|
134
|
-
seen.add(value);
|
|
135
|
-
if (Array.isArray(value)) {
|
|
136
|
-
return "[" + value.map(function (v) { return _canonicalize(v, seen); }).join(",") + "]";
|
|
137
|
-
}
|
|
138
|
-
var keys = Object.keys(value).sort();
|
|
139
|
-
var parts = [];
|
|
140
|
-
for (var i = 0; i < keys.length; i++) {
|
|
141
|
-
parts.push(JSON.stringify(keys[i]) + ":" + _canonicalize(value[keys[i]], seen));
|
|
110
|
+
// Canonical JSON via the shared lib/canonical-json walker with
|
|
111
|
+
// bufferAs: "reject" — cursor state must contain only plain data, so
|
|
112
|
+
// Buffer / Uint8Array reject loudly. Map / Set / RegExp / Symbol /
|
|
113
|
+
// function / circular always reject; Date → ISO; BigInt → decimal
|
|
114
|
+
// string. Wraps the walker's generic Error in a PaginationError so
|
|
115
|
+
// callers can route on `pagination/bad-state`.
|
|
116
|
+
function _canonicalize(value) {
|
|
117
|
+
try { return canonicalJson.stringify(value, { bufferAs: "reject" }); }
|
|
118
|
+
catch (e) {
|
|
119
|
+
throw new PaginationError("pagination/bad-state", e.message);
|
|
142
120
|
}
|
|
143
|
-
return "{" + parts.join(",") + "}";
|
|
144
121
|
}
|
|
145
122
|
|
|
146
123
|
function _toBuf(secret) {
|
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:3d7e40d6-b790-48ba-ab7c-c25a893da190",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-03T15:00:01.675Z",
|
|
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.68",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.68",
|
|
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.68",
|
|
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.68",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|