@blamejs/blamejs-shop 0.0.83 → 0.0.98
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 +30 -0
- package/lib/admin.js +11 -7
- package/lib/customer-import.js +1 -1
- package/lib/email-campaigns.js +1 -1
- package/lib/pwa-manifest.js +1 -0
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +8 -0
- package/lib/vendor/blamejs/CHANGELOG.md +6 -0
- package/lib/vendor/blamejs/README.md +1 -1
- package/lib/vendor/blamejs/SECURITY.md +1 -0
- package/lib/vendor/blamejs/api-snapshot.json +167 -2
- package/lib/vendor/blamejs/fuzz/safe-archive.fuzz.js +37 -0
- package/lib/vendor/blamejs/index.js +15 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +629 -0
- package/lib/vendor/blamejs/lib/archive-gz.js +229 -0
- package/lib/vendor/blamejs/lib/archive-read.js +781 -0
- package/lib/vendor/blamejs/lib/archive-tar-read.js +418 -0
- package/lib/vendor/blamejs/lib/archive-tar.js +571 -0
- package/lib/vendor/blamejs/lib/archive.js +24 -2
- package/lib/vendor/blamejs/lib/audit.js +22 -7
- package/lib/vendor/blamejs/lib/backup/index.js +469 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +180 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +205 -0
- package/lib/vendor/blamejs/lib/safe-archive.js +309 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.7.json +86 -0
- package/lib/vendor/blamejs/release-notes/v0.12.8.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.12.9.json +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-gz.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +180 -0
- package/package.json +2 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.archive.tar write + b.archive.read.tar +
|
|
4
|
+
* b.guardArchive.tarEntryPolicy + b.backup.migrate.
|
|
5
|
+
*
|
|
6
|
+
* Round-trip coverage: write tar via b.archive.tar(), read back via
|
|
7
|
+
* b.archive.read.tar(buffer adapter), extract via b.safeArchive.extract
|
|
8
|
+
* (format auto-detect lands on tar). Refusal: oversize entry, dangerous
|
|
9
|
+
* typeflag refusal by default. Migration: write a directory bundle in
|
|
10
|
+
* one storage, migrate to tar bundle in another, verify content.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
var helpers = require("../helpers");
|
|
14
|
+
var check = helpers.check;
|
|
15
|
+
var b = helpers.b;
|
|
16
|
+
var os = require("node:os");
|
|
17
|
+
var path = require("node:path");
|
|
18
|
+
var fs = require("node:fs");
|
|
19
|
+
|
|
20
|
+
async function testTarRoundTrip() {
|
|
21
|
+
var t = b.archive.tar();
|
|
22
|
+
t.addFile("readme.txt", "Hello, tar!\n");
|
|
23
|
+
t.addFile("data/numbers.csv", "n,sq\n1,1\n2,4\n");
|
|
24
|
+
t.addDirectory("docs/");
|
|
25
|
+
t.addFile("docs/nested/deep.txt", "payload\n");
|
|
26
|
+
var bytes = t.toBuffer();
|
|
27
|
+
|
|
28
|
+
check("archive.tar: tar bytes round to 512", bytes.length % 512 === 0);
|
|
29
|
+
check("archive.tar: ustar magic at offset 257",
|
|
30
|
+
bytes.slice(257, 263).toString().indexOf("ustar") === 0);
|
|
31
|
+
|
|
32
|
+
var reader = b.archive.read.tar(b.archive.adapters.buffer(bytes));
|
|
33
|
+
var entries = await reader.inspect();
|
|
34
|
+
check("archive.read.tar.inspect: 4 entries", entries.length === 4);
|
|
35
|
+
check("archive.read.tar.inspect: file type", entries[0].entryType === "file");
|
|
36
|
+
check("archive.read.tar.inspect: directory type", entries[2].entryType === "directory");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testSafeArchiveTarExtract() {
|
|
40
|
+
var t = b.archive.tar();
|
|
41
|
+
t.addFile("a.txt", "alpha");
|
|
42
|
+
t.addFile("nested/b.txt", "beta");
|
|
43
|
+
var bytes = t.toBuffer();
|
|
44
|
+
|
|
45
|
+
var dest = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-tar-extract-"));
|
|
46
|
+
try {
|
|
47
|
+
var result = await b.safeArchive.extract({
|
|
48
|
+
source: bytes,
|
|
49
|
+
destination: dest,
|
|
50
|
+
guardProfile: "balanced",
|
|
51
|
+
});
|
|
52
|
+
check("safeArchive.extract: format=tar autodetected", result.format === "tar");
|
|
53
|
+
check("safeArchive.extract: 2 entries written", result.entries.length === 2);
|
|
54
|
+
var a = fs.readFileSync(path.join(dest, "a.txt"), "utf8");
|
|
55
|
+
var bb = fs.readFileSync(path.join(dest, "nested", "b.txt"), "utf8");
|
|
56
|
+
check("safeArchive.extract: a.txt contents", a === "alpha");
|
|
57
|
+
check("safeArchive.extract: nested b.txt contents", bb === "beta");
|
|
58
|
+
} finally {
|
|
59
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function testTarEntryPolicy() {
|
|
64
|
+
var p = b.guardArchive.tarEntryPolicy({ symlinks: true });
|
|
65
|
+
check("tarEntryPolicy: symlinks opted in", p.symlinks === true);
|
|
66
|
+
check("tarEntryPolicy: hardlinks default off", p.hardlinks === false);
|
|
67
|
+
check("tarEntryPolicy: devices default off", p.devices === false);
|
|
68
|
+
check("tarEntryPolicy: fifos default off", p.fifos === false);
|
|
69
|
+
check("tarEntryPolicy: sockets default off", p.sockets === false);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function testTarChecksumDetection() {
|
|
73
|
+
// Build a valid tar, corrupt the chksum field, verify reader refuses.
|
|
74
|
+
var t = b.archive.tar();
|
|
75
|
+
t.addFile("payload.txt", "hello\n");
|
|
76
|
+
var bytes = t.toBuffer();
|
|
77
|
+
// Corrupt the checksum field of the first header (offset 148, 8 bytes).
|
|
78
|
+
bytes[148] = 0x39; // change first chksum digit
|
|
79
|
+
var refused = null;
|
|
80
|
+
try {
|
|
81
|
+
var reader = b.archive.read.tar(b.archive.adapters.buffer(bytes));
|
|
82
|
+
await reader.inspect();
|
|
83
|
+
} catch (e) { refused = e; }
|
|
84
|
+
check("archive.read.tar: corrupted chksum refused",
|
|
85
|
+
refused && /chksum|bad-octal/.test(refused.code || refused.message));
|
|
86
|
+
check("archive.read.tar: chksum refusal is a b.archive.TarError",
|
|
87
|
+
refused instanceof b.archive.TarError);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function testBackupMigrateDirectoryToTar() {
|
|
91
|
+
var fromRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-from-"));
|
|
92
|
+
var toRoot = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-to-"));
|
|
93
|
+
var srcDir = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-src-"));
|
|
94
|
+
var verifyDir = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-verify-"));
|
|
95
|
+
fs.rmSync(verifyDir, { recursive: true }); // backend wants non-existent dest
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(path.join(srcDir, "manifest.json"), JSON.stringify({ v: 1 }));
|
|
98
|
+
fs.mkdirSync(path.join(srcDir, "files"));
|
|
99
|
+
fs.writeFileSync(path.join(srcDir, "files", "blob.bin"), Buffer.from([1, 2, 3]));
|
|
100
|
+
|
|
101
|
+
var from = b.backup.bundleAdapterStorage({
|
|
102
|
+
adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: fromRoot }),
|
|
103
|
+
format: "directory",
|
|
104
|
+
});
|
|
105
|
+
var to = b.backup.bundleAdapterStorage({
|
|
106
|
+
adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: toRoot }),
|
|
107
|
+
format: "tar",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
var bundleId = "2026-05-23T15-00-00-000Z-deadbeef";
|
|
111
|
+
await from.writeBundle(bundleId, srcDir);
|
|
112
|
+
check("migrate: source-bundle exists pre-migrate", await from.hasBundle(bundleId));
|
|
113
|
+
check("migrate: dest-bundle absent pre-migrate", !(await to.hasBundle(bundleId)));
|
|
114
|
+
|
|
115
|
+
var report = await b.backup.migrate({ from: from, to: to });
|
|
116
|
+
check("migrate: 1 migrated", report.migrated === 1 && report.skipped === 0);
|
|
117
|
+
check("migrate: dest-bundle present post-migrate", await to.hasBundle(bundleId));
|
|
118
|
+
|
|
119
|
+
// Idempotency: second run skips.
|
|
120
|
+
var second = await b.backup.migrate({ from: from, to: to });
|
|
121
|
+
check("migrate: idempotent (1 skipped)", second.migrated === 0 && second.skipped === 1);
|
|
122
|
+
|
|
123
|
+
// Read back from destination, verify file contents.
|
|
124
|
+
await to.readBundle(bundleId, verifyDir);
|
|
125
|
+
var manifest = JSON.parse(fs.readFileSync(path.join(verifyDir, "manifest.json"), "utf8"));
|
|
126
|
+
check("migrate: manifest round-tripped", manifest.v === 1);
|
|
127
|
+
var blob = fs.readFileSync(path.join(verifyDir, "files", "blob.bin"));
|
|
128
|
+
check("migrate: blob round-tripped",
|
|
129
|
+
blob.length === 3 && blob[0] === 1 && blob[2] === 3);
|
|
130
|
+
} finally {
|
|
131
|
+
try { fs.rmSync(fromRoot, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
132
|
+
try { fs.rmSync(toRoot, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
133
|
+
try { fs.rmSync(srcDir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
134
|
+
try { fs.rmSync(verifyDir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function testBackupTarFormatDefault() {
|
|
139
|
+
// Write a bundle via the v0.12.8-default format (tar) + verify the
|
|
140
|
+
// tar key landed on disk.
|
|
141
|
+
var root = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-tar-default-"));
|
|
142
|
+
var src = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-tar-src-"));
|
|
143
|
+
try {
|
|
144
|
+
fs.writeFileSync(path.join(src, "manifest.json"), JSON.stringify({ v: 1 }));
|
|
145
|
+
var storage = b.backup.bundleAdapterStorage({
|
|
146
|
+
adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: root }),
|
|
147
|
+
// format defaults to "tar" in v0.12.8
|
|
148
|
+
});
|
|
149
|
+
var bid = "2026-05-23T15-30-00-000Z-cafebabe";
|
|
150
|
+
await storage.writeBundle(bid, src);
|
|
151
|
+
var tarKeyPresent = fs.existsSync(path.join(root, bid, "bundle.tar"));
|
|
152
|
+
check("bundleAdapterStorage default format: tar key on disk", tarKeyPresent);
|
|
153
|
+
check("bundleAdapterStorage default format: hasBundle true",
|
|
154
|
+
await storage.hasBundle(bid));
|
|
155
|
+
} finally {
|
|
156
|
+
try { fs.rmSync(root, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
157
|
+
try { fs.rmSync(src, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function testTarTruncationRefused() {
|
|
162
|
+
// Codex P1 on v0.12.8 PR #159 — declare 11 bytes in the header
|
|
163
|
+
// but truncate the buffer to 8 payload bytes. The walker must
|
|
164
|
+
// refuse upfront rather than emitting a partial entry.
|
|
165
|
+
var t = b.archive.tar();
|
|
166
|
+
t.addFile("payload.txt", Buffer.from("hello world")); // 11 bytes
|
|
167
|
+
var bytes = t.toBuffer();
|
|
168
|
+
// First header block is at offset 0-511; first data block at
|
|
169
|
+
// 512-1023 (padded to 512 bytes from the declared 11). Truncate to
|
|
170
|
+
// 768 bytes: header survives intact but the data block ends
|
|
171
|
+
// halfway — walker must refuse before slicing partial bytes as
|
|
172
|
+
// a "complete" file.
|
|
173
|
+
var truncated = bytes.slice(0, 768);
|
|
174
|
+
var refused = null;
|
|
175
|
+
try {
|
|
176
|
+
var reader = b.archive.read.tar(b.archive.adapters.buffer(truncated));
|
|
177
|
+
await reader.extract({
|
|
178
|
+
destination: fs.mkdtempSync(path.join(os.tmpdir(), "bjs-tar-trunc-")),
|
|
179
|
+
});
|
|
180
|
+
} catch (e) { refused = e; }
|
|
181
|
+
check("archive.read.tar: truncated entry refused with typed error",
|
|
182
|
+
refused && /truncated-entry/.test(refused.code || refused.message));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function testBackupBundleTooLargeRefused() {
|
|
186
|
+
// Codex P2 on v0.12.8 PR #159 — bundleAdapterStorage with a
|
|
187
|
+
// tight maxBundleBytes cap must refuse oversized payloads upfront
|
|
188
|
+
// rather than OOM during in-memory tar materialization.
|
|
189
|
+
var srcDir = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-bulk-"));
|
|
190
|
+
var dest = fs.mkdtempSync(path.join(os.tmpdir(), "bjs-bk-bulk-dest-"));
|
|
191
|
+
try {
|
|
192
|
+
fs.writeFileSync(path.join(srcDir, "big.bin"), Buffer.alloc(64 * 1024));
|
|
193
|
+
var storage = b.backup.bundleAdapterStorage({
|
|
194
|
+
adapter: b.backup.bundleAdapterStorage.fsAdapter({ root: dest }),
|
|
195
|
+
format: "tar",
|
|
196
|
+
maxBundleBytes: 16 * 1024,
|
|
197
|
+
});
|
|
198
|
+
var refused = null;
|
|
199
|
+
try {
|
|
200
|
+
await storage.writeBundle("2026-05-23T00-00-00-000Z-abcdef12", srcDir);
|
|
201
|
+
} catch (e) { refused = e; }
|
|
202
|
+
check("backup: bundle exceeding maxBundleBytes refused upfront",
|
|
203
|
+
refused && /bundle-too-large/.test(refused.code || refused.message));
|
|
204
|
+
} finally {
|
|
205
|
+
try { fs.rmSync(srcDir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
206
|
+
try { fs.rmSync(dest, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function run() {
|
|
211
|
+
await testTarRoundTrip();
|
|
212
|
+
await testSafeArchiveTarExtract();
|
|
213
|
+
testTarEntryPolicy();
|
|
214
|
+
await testTarChecksumDetection();
|
|
215
|
+
await testTarTruncationRefused();
|
|
216
|
+
await testBackupMigrateDirectoryToTar();
|
|
217
|
+
await testBackupTarFormatDefault();
|
|
218
|
+
await testBackupBundleTooLargeRefused();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = { run: run };
|
|
222
|
+
|
|
223
|
+
if (require.main === module) {
|
|
224
|
+
run().then(
|
|
225
|
+
function () { console.log("[archive-tar] OK — " + helpers.getChecks() + " checks passed"); },
|
|
226
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -2185,6 +2185,76 @@ async function testNoDuplicateCodeBlocks() {
|
|
|
2185
2185
|
files: ["lib/api-key.js:issue", "lib/db-query.js:<top>", "lib/session.js:create"],
|
|
2186
2186
|
reason: "Generic JS array helper / lambda shape — Object.keys(...).map(fn) + similar functional idioms appearing in any code that walks a column-or-key list.",
|
|
2187
2187
|
},
|
|
2188
|
+
{
|
|
2189
|
+
mode: "family-subset",
|
|
2190
|
+
files: [
|
|
2191
|
+
"lib/archive-read.js:_emitAudit",
|
|
2192
|
+
"lib/archive-tar-read.js:_emitAudit",
|
|
2193
|
+
"lib/archive.js:_emitAudit",
|
|
2194
|
+
"lib/http-client.js:_emitAudit",
|
|
2195
|
+
],
|
|
2196
|
+
reason: "v0.12.7 + v0.12.8 — Per-module `_emitAudit(opts, action, outcome, metadata)` shape repeats across primitives that drop-silently emit to opts.audit.safeEmit if present. Each module's audit events carry a primitive-specific `action:` namespace (archive.read.*, archive.zip.*, archive.read.tar.*, http-client.*) + per-primitive metadata fields; consolidating would lose the namespace + force every consumer to import the same audit helper. Four-file repetition is the expected shape per `feedback_audit_safeEmit_per_module_emitAudit_shape`. archive-tar.js (write) does NOT carry _emitAudit — the read side lives in sibling archive-tar-read.js so the @primitive validator can pair both `b.archive.tar` (write) and `b.archive.read.tar` (read) cleanly.",
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
mode: "family-subset",
|
|
2200
|
+
files: [
|
|
2201
|
+
"lib/archive-adapters.js:fs",
|
|
2202
|
+
"lib/archive-adapters.js:http",
|
|
2203
|
+
"lib/network-smtp-policy.js:mtaStsFetch",
|
|
2204
|
+
"lib/parsers/safe-env.js:readVar",
|
|
2205
|
+
],
|
|
2206
|
+
reason: "v0.12.7 — `if (typeof <opt> !== \"string\" || <opt>.length === 0) throw new <Error>(...)` shape repeats across primitives validating REQUIRED string opts. validateOpts.requireNonEmptyString covers most call sites; the four flagged here are inline because they each carry a primitive-specific error CODE (adapter/bad-arg, smtp-policy/bad-arg, safe-env/bad-arg) that the helper's caller-error-class shape doesn't compose cleanly across — each primitive's typed-error class is module-local + the message string names the local opt. The duplicated shape is the symptom, not the cause; the cause is that JS doesn't have a way to throw an instance of caller-namespaced ErrorClass without the local closure.",
|
|
2207
|
+
},
|
|
2208
|
+
{
|
|
2209
|
+
mode: "family-subset",
|
|
2210
|
+
files: [
|
|
2211
|
+
"lib/archive-read.js:extract",
|
|
2212
|
+
"lib/archive-tar-read.js:extract",
|
|
2213
|
+
"lib/auth/ciba.js:pollToken",
|
|
2214
|
+
"lib/auth/oid4vci.js:exchangePreAuthorizedCode",
|
|
2215
|
+
"lib/auth/oid4vci.js:issueCredential",
|
|
2216
|
+
],
|
|
2217
|
+
reason: "v0.12.7 + v0.12.8 — `try { ... await ... } catch (e) { /* per-step cleanup */ throw e; }` shape repeats across primitives doing multi-step async work with per-step rollback. archive-read.extract + archive-tar.extract both clean up partial-extract files; ciba.pollToken cleans up rate-limit + retry state; oid4vci.exchange/issueCredential clean up partial credential-state. Each catch body is primitive-specific (the cleanup it does is the primitive's responsibility) — extraction would require a generic transaction-style helper which is itself a v1.0+ surface decision. Five-file repetition with primitive-specific cleanup bodies stays as the documented exception.",
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
mode: "family-subset",
|
|
2221
|
+
files: [
|
|
2222
|
+
"lib/archive-tar-read.js:_classifyTypeflag",
|
|
2223
|
+
"lib/archive-tar-read.js:inspect",
|
|
2224
|
+
"lib/auth/ciba.js:_registerInitialInterval",
|
|
2225
|
+
"lib/auth/oauth.js:exchangeToken",
|
|
2226
|
+
"lib/auth/oauth.js:pollDeviceCode",
|
|
2227
|
+
"lib/auth/oid4vci.js:createCredentialOffer",
|
|
2228
|
+
"lib/auth/oid4vci.js:exchangePreAuthorizedCode",
|
|
2229
|
+
"lib/restore-rollback.js:swap",
|
|
2230
|
+
],
|
|
2231
|
+
reason: "v0.12.8 — Compact branching helpers (`if (x === A) return ...; if (x === B) return ...;` switch-style) repeat across primitives that map operator-supplied enum values to internal labels. archive-tar-read._classifyTypeflag maps single-char tar typeflags (0/1/2/3/4/5/6/7/x/g) to entry-type labels (file/symlink/hardlink/device/fifo/directory/etc.); archive-tar-read.inspect dispatches on the same typeflag set per-entry — distinct vocabulary from oauth.exchangeToken / oid4vci.createCredentialOffer / etc. which dispatch on grant_type / credential_format / step. The match is shape (chain of if-equals-return), not semantic. Extraction would require a generic enum-dispatch helper for trivially-different enums — that's an obscured abstraction. Each call site's enum + label set is primitive-specific.",
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
mode: "family-subset",
|
|
2235
|
+
files: [
|
|
2236
|
+
"lib/archive-read.js:_normalizeEntryTypePolicy",
|
|
2237
|
+
"lib/archive-tar-read.js:_normalizeEntryTypePolicy",
|
|
2238
|
+
"lib/archive.js:writeTo",
|
|
2239
|
+
],
|
|
2240
|
+
reason: "v0.12.8 — `_normalizeEntryTypePolicy` shape is genuinely duplicated between archive-read.js + archive-tar-read.js — both copy DEFAULT_ENTRY_TYPE_POLICY and merge with operator opts. Could extract to a shared lib/_archive-policy.js helper in a future patch; for v0.12.8 keeping the duplication so the format-specific entry-type vocabulary (zip's external-attrs vs tar's typeflag) stays close to the reader that uses it. archive.js:writeTo is the unrelated third file in the dup cluster — its toBuffer + writeFileSync shape happens to share the 50-token shingle by coincidence (writeTo is the legacy ZIP write-to-path helper, not policy-related).",
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
mode: "family-subset",
|
|
2244
|
+
files: [
|
|
2245
|
+
"lib/agent-idempotency.js:_checkArgs",
|
|
2246
|
+
"lib/agent-tenant.js:_sealField",
|
|
2247
|
+
"lib/atomic-file.js:copyDirRecursive",
|
|
2248
|
+
"lib/ddl-change-control.js:approve",
|
|
2249
|
+
"lib/ddl-change-control.js:reject",
|
|
2250
|
+
"lib/deprecate.js:alias",
|
|
2251
|
+
"lib/guard-filename.js:verifyExtractionPath",
|
|
2252
|
+
"lib/jose-jwe-experimental.js:decrypt",
|
|
2253
|
+
"lib/mail-deploy.js:_validateTlsRptReport",
|
|
2254
|
+
"lib/totp.js:uri",
|
|
2255
|
+
],
|
|
2256
|
+
reason: "v0.12.7 — Generic string-argument validation shape: `if (typeof X !== \"string\" || X.length === 0) throw new <ErrorClass>(<code>, ...)` repeats across primitives validating REQUIRED non-empty string opts. Each call site emits a primitive-specific typed error class (BackupError, GuardFilenameError, IdempotencyError, AgentTenantError, AtomicFileError, DdlError, DeprecateError, JoseError, MailDeployError, TotpError) so extracting to a shared helper would lose the per-primitive error namespace. validateOpts.requireNonEmptyString covers most call sites where the caller's typed-error class composes with the helper's caller-error-class shape; the 9 file paths here are inline because each one's typed error has a primitive-local code namespace + message string the helper can't compose cleanly. Same shape as the v0.10.16 client-hints/csp/sandbox family-subset reason (inline for per-primitive typed errors). 5/9/3-file subsets at smaller token windows are the same family — one entry covers all of them.",
|
|
2257
|
+
},
|
|
2188
2258
|
{
|
|
2189
2259
|
mode: "family-subset",
|
|
2190
2260
|
files: [
|
|
@@ -5391,7 +5461,10 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
5391
5461
|
"lib/observability-otlp-exporter.js", // byResource grouping (Map<resKey, bucket>) — object-literal factory
|
|
5392
5462
|
"lib/otel-export.js", // counters / observations (2 sites) — object-literal factory
|
|
5393
5463
|
"lib/pubsub.js", // exactSubs (Map<channel, Set<sub>>) — Set factory
|
|
5464
|
+
"lib/backup/index.js", // bundleAdapterStorage.listBundles: byBundle (Map<bundleId, stats>) — object-literal factory
|
|
5394
5465
|
],
|
|
5466
|
+
// Strong-dup allowlists added with v0.12.7 archive substrate
|
|
5467
|
+
// — see KNOWN_CLUSTERS additions below for structural reasons.
|
|
5395
5468
|
reason: "Node 26 ships Map.prototype.getOrInsertComputed(key, factory) — a single-lookup get-or-insert that replaces the two-step `var v = m.get(k); if (!v) { v = factory(); m.set(k, v); }` pattern. The sweep is deferred to the Node 26 floor-bump (eligible Oct 2026); engines.node is `>=24` today. Allowlist above is the survey ground truth from memory/specs/node-26-map-getorinsert-migration.md. New code post-this-patch trips the detector — either wait for the floor bump, or add the call site to BOTH the allowlist AND the migration spec in the same patch. When the floor moves, the bump commit walks the allowlist, rewrites each call site, drops the allowlist + flips the detector to enforce.",
|
|
5396
5469
|
},
|
|
5397
5470
|
{
|
|
@@ -5512,6 +5585,113 @@ var KNOWN_ANTIPATTERNS = [
|
|
|
5512
5585
|
reason: "Codex P1 on v0.12.6 PR #157 — `_anyValueToProto` negative-int path wrapped a varint payload in `embeddedMessage` (wire-type 2) instead of using `int64` (wire-type 0 varint). The wire-type mismatch poisons the whole OTLP batch; the `v >>> 0` truncation also dropped sign + high bits. Fixed by adding `pb.int64` + `pb.sint64` to the encoder + routing the negative-int branch through `pb.int64`. Detector locks the shape: `embeddedMessage(N, _writeVarint(...))` cannot recur.",
|
|
5513
5586
|
},
|
|
5514
5587
|
|
|
5588
|
+
{
|
|
5589
|
+
// Codex P1 on v0.12.7 PR #158 — archive-read.extract's rollback
|
|
5590
|
+
// cleanup deleted PRE-EXISTING destination files when a later
|
|
5591
|
+
// entry failed. The renameSync(tmpPath, resolvedPath) silently
|
|
5592
|
+
// overwrote operator files at the destination, then on abort the
|
|
5593
|
+
// catch-block rmSync wiped them out — permanent data loss
|
|
5594
|
+
// disguised as atomic rollback. Fix: refuse to write when the
|
|
5595
|
+
// destination path already exists; force operators to extract
|
|
5596
|
+
// into a fresh / empty subtree.
|
|
5597
|
+
//
|
|
5598
|
+
// Detector scope: any lib/archive*.js or lib/safe-archive.js file
|
|
5599
|
+
// that calls renameSync into a path it ALSO tracks for cleanup
|
|
5600
|
+
// MUST refuse overwrite up-front. Codify as a file-scoped invariant:
|
|
5601
|
+
// archive-read.js must contain "destination-exists" refusal code.
|
|
5602
|
+
id: "archive-extract-overwrite-without-refusal",
|
|
5603
|
+
primitive: "extract loops in lib/archive-read.js MUST refuse to write to a destination path that already exists — atomic rollback via tmp-rename + tracked-path cleanup is only safe when every tracked path was newly created. Pre-existing files at the destination + catch-block rmSync = data loss.",
|
|
5604
|
+
// File-scoped: only fires on archive-read.js / safe-archive.js
|
|
5605
|
+
// shape. The pattern is renameSync of a tmpPath onto resolvedPath
|
|
5606
|
+
// (the canonical destination variable) — atomic-file.js's
|
|
5607
|
+
// operator-file rename is a different shape (operator already
|
|
5608
|
+
// owns the destination context); http-client.js's atomic-tmp
|
|
5609
|
+
// rename writes operator-supplied paths under operator-supplied
|
|
5610
|
+
// tmp dirs, also a different concern.
|
|
5611
|
+
regex: /written\.push\s*\(\s*\{[^}]*path:\s*resolvedPath/,
|
|
5612
|
+
requires: /destination-exists/,
|
|
5613
|
+
skipCommentLines: true,
|
|
5614
|
+
allowlist: [],
|
|
5615
|
+
reason: "Codex P1 on v0.12.7 PR #158 — archive-read.extract used renameSync to atomically place each decompressed entry at its canonical destination + tracked written[].path for catch-block cleanup. When the destination directory was non-empty, the rename silently overwrote operator files; on extract abort, the cleanup deleted them. Fix: refuse upfront if destination path exists, force operators to use a fresh / empty subtree. Detector locks the shape: any extract code that tracks resolvedPath for catch-block cleanup MUST carry a `destination-exists` refusal in the same file.",
|
|
5616
|
+
},
|
|
5617
|
+
|
|
5618
|
+
{
|
|
5619
|
+
// Codex P1 + P2 on v0.12.9 PR #160 — backup readBundle's
|
|
5620
|
+
// tar.gz restore path inherited archive.read.gz defaults (1 GiB
|
|
5621
|
+
// output / 100× ratio), which made the SAME primitive write
|
|
5622
|
+
// bundles it couldn't read back. The detector enforces the
|
|
5623
|
+
// write/read contract for self-authored gzip payloads: any
|
|
5624
|
+
// lib/ call to `archive.read.gz(...)` from a context that has
|
|
5625
|
+
// its own size budget (paired with a `maxBundleBytes` /
|
|
5626
|
+
// `maxOutputBytes` / `maxPayloadBytes` opt) MUST propagate
|
|
5627
|
+
// that budget to read.gz via `maxDecompressedBytes` AND
|
|
5628
|
+
// disable the ratio cap (`maxExpansionRatio: 0`) — bombs in
|
|
5629
|
+
// self-authored payloads are already prevented at write time.
|
|
5630
|
+
id: "archive-read-gz-without-self-authored-budget",
|
|
5631
|
+
primitive: "callers of archive.read.gz from a context that gates its own writes on a size cap (maxBundleBytes / similar) must pass maxDecompressedBytes + maxExpansionRatio:0 so the write/read contract is symmetric. Bomb defenses live at the upstream cap; the gz layer just decompresses.",
|
|
5632
|
+
// File-scoped: only fires on backup/index.js shapes for now.
|
|
5633
|
+
// archive.read.gz called with no opts is fine in operator code
|
|
5634
|
+
// (adversarial-input case); the antipattern is when the caller
|
|
5635
|
+
// also writes payloads under its own size cap.
|
|
5636
|
+
regex: /archive(?:Lazy\(\))?\.read\.gz\s*\([^)]*\)\s*[^,{]/,
|
|
5637
|
+
requires: /maxDecompressedBytes/,
|
|
5638
|
+
skipCommentLines: true,
|
|
5639
|
+
allowlist: [
|
|
5640
|
+
// archive-gz.js IS the read.gz primitive itself.
|
|
5641
|
+
"lib/archive-gz.js",
|
|
5642
|
+
],
|
|
5643
|
+
reason: "Codex P1/P2 on v0.12.9 PR #160 — backup readBundle's tar.gz restore inherited the 100× ratio + 1 GiB output defaults, breaking restore for zero-filled DB dumps + ~1-8 GiB bundles that writeBundle accepts. Fix: every archive.read.gz call from a primitive with its own size budget propagates that budget. Detector locks the symmetry.",
|
|
5644
|
+
},
|
|
5645
|
+
|
|
5646
|
+
{
|
|
5647
|
+
// v0.12.9 — Direct node:zlib gunzip calls in lib/ must compose
|
|
5648
|
+
// b.safeDecompress (1 GiB output / 100× ratio default caps) so a
|
|
5649
|
+
// hostile gzip stream can't OOM or expand-bomb the host. Mirrors
|
|
5650
|
+
// the v0.11.5 must-compose pattern. lib/archive-gz.js IS the
|
|
5651
|
+
// canonical gunzip site (it wires safeDecompress in directly);
|
|
5652
|
+
// every other lib/ call to zlib.gunzipSync / zlib.createGunzip
|
|
5653
|
+
// must either route through b.safeDecompress OR carry a marker
|
|
5654
|
+
// explaining why it's safe to bypass (e.g. the caller already
|
|
5655
|
+
// applied `maxOutputLength` AND the input is operator-controlled).
|
|
5656
|
+
id: "archive-gz-without-safedecompress",
|
|
5657
|
+
primitive: "every lib/ call to zlib.gunzipSync / zlib.createGunzip / gunzip MUST either go through lib/archive-gz.js (which composes b.safeDecompress) OR carry an `allow:archive-gz-without-safedecompress` marker with the reason the bomb gate is bypassed (typically: `maxOutputLength` is already enforced + the input is operator-trusted).",
|
|
5658
|
+
regex: /zlib\.(?:gunzipSync|createGunzip)\b/,
|
|
5659
|
+
requires: /safeDecompress|maxOutputLength|allow:archive-gz-without-safedecompress/,
|
|
5660
|
+
skipCommentLines: true,
|
|
5661
|
+
allowlist: [
|
|
5662
|
+
// archive-gz.js is the canonical gunzip site — it directly
|
|
5663
|
+
// imports safeDecompress and routes every call through it.
|
|
5664
|
+
// Listed here so the detector doesn't false-positive against
|
|
5665
|
+
// its own enforcement file.
|
|
5666
|
+
"lib/archive-gz.js",
|
|
5667
|
+
],
|
|
5668
|
+
reason: "v0.12.9 — b.archive.read.gz is the framework's gzip read primitive and composes b.safeDecompress for every gunzip. Direct lib/ zlib.gunzipSync / zlib.createGunzip calls must either route through b.archive.read.gz, compose b.safeDecompress inline, OR carry an explicit `maxOutputLength` cap with the bypass marker. The detector locks the contract so v0.13+ primitives that handle a gzip-wrapped payload can't quietly drop the bomb cap.",
|
|
5669
|
+
},
|
|
5670
|
+
|
|
5671
|
+
{
|
|
5672
|
+
// Codex P1 on v0.12.8 PR #159 — archive-tar-read.js's walker
|
|
5673
|
+
// advanced `pos` by the declared padded block size without
|
|
5674
|
+
// checking that those bytes existed in the buffer. A truncated
|
|
5675
|
+
// archive (header says 11 bytes, buffer holds 8) silently
|
|
5676
|
+
// produced an entry whose extract() sliced the 8-byte prefix
|
|
5677
|
+
// and wrote it as if it were the complete file. Fix: refuse
|
|
5678
|
+
// upfront with a `truncated-entry` typed error when
|
|
5679
|
+
// `bodyStart + paddedSize > bytes.length`. Same shape applies
|
|
5680
|
+
// to the pax-extended-header path (its `bodyEnd` advance was
|
|
5681
|
+
// the same uncapped arithmetic).
|
|
5682
|
+
id: "archive-tar-walker-without-truncation-check",
|
|
5683
|
+
primitive: "tar walkers in lib/archive-tar-read.js MUST verify that the declared block size fits within the remaining buffer before advancing `pos` — a header that claims more bytes than the buffer holds is a truncated archive, not a valid entry. The refusal carries `truncated-entry` code so operators can distinguish wire-format-bad input from policy-bad input.",
|
|
5684
|
+
// File-scoped: only fires on archive-tar-read.js. The walker
|
|
5685
|
+
// advances pos by paddedSize (Math.ceil(hdr.size / BLOCK_SIZE)
|
|
5686
|
+
// * BLOCK_SIZE) — any code that adds paddedSize to pos without
|
|
5687
|
+
// a preceding bounds check is the smell.
|
|
5688
|
+
regex: /pos\s*\+=\s*paddedSize/,
|
|
5689
|
+
requires: /truncated-entry/,
|
|
5690
|
+
skipCommentLines: true,
|
|
5691
|
+
allowlist: [],
|
|
5692
|
+
reason: "Codex P1 on v0.12.8 PR #159 — archive-tar-read.js's tar walker recorded each entry and advanced pos by paddedSize without verifying the declared bytes existed in the buffer. A truncated archive silently produced a partial-content entry on extract — exact reproducer in the Codex thread: declared 11-byte file backed by 8 bytes of buffer produced an 8-byte output. Fix: refuse upfront with `archive-tar/truncated-entry` typed error. Detector locks the shape: any code path that advances pos by paddedSize in archive-tar-read.js MUST carry a `truncated-entry` refusal in the same file.",
|
|
5693
|
+
},
|
|
5694
|
+
|
|
5515
5695
|
{
|
|
5516
5696
|
// Codex P2 on v0.11.22 PR #126 — `b.cert.create`'s SNI dispatch
|
|
5517
5697
|
// wildcard-matched `*.example.com` against `foo.bar.example.com`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/blamejs-shop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.98",
|
|
4
4
|
"description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"type": "git",
|
|
36
36
|
"url": "git+https://github.com/blamejs/blamejs.shop.git"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://
|
|
38
|
+
"homepage": "https://blamejs.shop",
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@cloudflare/containers": "^0.3.4",
|
|
41
41
|
"wrangler": "^4.93.0"
|