@blamejs/blamejs-shop 0.4.87 → 0.4.89
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/SECURITY.md +22 -9
- package/lib/asset-manifest.json +1 -1
- package/lib/gift-card-ledger.js +72 -15
- package/lib/store-credit.js +340 -89
- package/lib/vendor/MANIFEST.json +53 -31
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +7 -2
- package/lib/vendor/blamejs/.github/dependabot.yml +12 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +16 -12
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +3 -1
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +23 -0
- package/lib/vendor/blamejs/CHANGELOG.md +6 -0
- package/lib/vendor/blamejs/api-snapshot.json +10 -2
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +19 -2
- package/lib/vendor/blamejs/lib/atomic-file.js +32 -9
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +105 -40
- package/lib/vendor/blamejs/lib/middleware/dpop.js +54 -31
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +7 -0
- package/lib/vendor/blamejs/lib/network-tls.js +12 -2
- package/lib/vendor/blamejs/lib/queue-local.js +123 -46
- package/lib/vendor/blamejs/lib/queue.js +13 -9
- package/lib/vendor/blamejs/lib/request-helpers.js +90 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +69 -7
- package/lib/vendor/blamejs/lib/self-update.js +11 -2
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +11 -11
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +6 -2
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +689 -688
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +5 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.17.json +52 -0
- package/lib/vendor/blamejs/release-notes/v0.15.18.json +49 -0
- package/lib/vendor/blamejs/release-notes/v0.15.19.json +18 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +24 -0
- package/lib/vendor/blamejs/test/integration/queue-cluster-mysql.test.js +419 -0
- package/lib/vendor/blamejs/test/integration/queue-cluster-pg.test.js +471 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt-rejection-envelope.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +35 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read-errorfor-bypass.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-htu-peergating.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-poll-asset-digest.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier-ecdsa-encoding.test.js +274 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-freshness.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-currency-classify.test.js +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.network.tls.ocsp.evaluate — OCSP response FRESHNESS enforcement
|
|
4
|
+
* (RFC 6960 §4.2.2.1). Regression coverage for the dead staleness gate:
|
|
5
|
+
* evaluateOcspResponse called Date.parse() on thisUpdate/nextUpdate, but
|
|
6
|
+
* those fields are already unix-ms NUMBERS (parseOcspResponse → _parseTime
|
|
7
|
+
* returns Date.UTC(...)). Date.parse(<number>) coerces to a bare-integer
|
|
8
|
+
* string → NaN, so the !isFinite guard rejected EVERY signature-valid
|
|
9
|
+
* response (fresh or stale) with a misleading "missing thisUpdate", leaving
|
|
10
|
+
* the real future-thisUpdate / past-nextUpdate window checks as unreachable
|
|
11
|
+
* dead code (the past-nextUpdate branch latently fail-open).
|
|
12
|
+
*
|
|
13
|
+
* No existing test built a full SIGNED `successful` BasicOCSPResponse — every
|
|
14
|
+
* prior OCSP test short-circuited before the freshness gate — which is why
|
|
15
|
+
* the dead check shipped. This builds a real ECDSA-SHA256-signed response and
|
|
16
|
+
* drives the consumer path, asserting:
|
|
17
|
+
* - a STALE response (past nextUpdate) is REJECTED for the stale reason
|
|
18
|
+
* (RED before the fix: rejected, but with "missing thisUpdate");
|
|
19
|
+
* - a FRESH response is ACCEPTED (RED before the fix: rejected as "missing
|
|
20
|
+
* thisUpdate" — the bug cannot tell fresh from stale);
|
|
21
|
+
* - a FUTURE-thisUpdate response is rejected for the future reason.
|
|
22
|
+
*
|
|
23
|
+
* Run standalone: node test/layer-0-primitives/tls-ocsp-freshness.test.js
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
var nodeCrypto = require("node:crypto");
|
|
27
|
+
var helpers = require("../helpers");
|
|
28
|
+
var b = helpers.b;
|
|
29
|
+
var check = helpers.check;
|
|
30
|
+
var asn1 = require("../../lib/asn1-der");
|
|
31
|
+
|
|
32
|
+
// A fixed reference time so the test is wall-clock independent (passed to
|
|
33
|
+
// evaluate as opts.now). All thisUpdate/nextUpdate are offsets from this.
|
|
34
|
+
var FIXED_NOW = 1750000000000; // 2025-06-15T...Z, a stable ms value
|
|
35
|
+
|
|
36
|
+
function _pad(n, width) {
|
|
37
|
+
var s = String(n);
|
|
38
|
+
while (s.length < width) s = "0" + s;
|
|
39
|
+
return s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// unix-ms → GeneralizedTime "YYYYMMDDhhmmssZ" (the on-the-wire shape
|
|
43
|
+
// _parseTime accepts; it truncates to whole seconds, which is fine — the
|
|
44
|
+
// offsets here are minutes/days, far from the second boundary).
|
|
45
|
+
function _genTime(ms) {
|
|
46
|
+
var d = new Date(ms);
|
|
47
|
+
return asn1.writeNode(0x18, Buffer.from(
|
|
48
|
+
_pad(d.getUTCFullYear(), 4) + _pad(d.getUTCMonth() + 1, 2) + _pad(d.getUTCDate(), 2) +
|
|
49
|
+
_pad(d.getUTCHours(), 2) + _pad(d.getUTCMinutes(), 2) + _pad(d.getUTCSeconds(), 2) + "Z",
|
|
50
|
+
"ascii"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Build a complete, validly-signed OCSP "successful" response (certStatus
|
|
54
|
+
// good) for a given serial, with the supplied thisUpdate/nextUpdate ms.
|
|
55
|
+
// Returns { der, issuerPem, serialHex }.
|
|
56
|
+
function _buildSignedOcsp(opts) {
|
|
57
|
+
var kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
58
|
+
var issuerPem = kp.publicKey.export({ type: "spki", format: "pem" });
|
|
59
|
+
|
|
60
|
+
var serial = Buffer.from([0x12, 0x34, 0x56, 0x78]);
|
|
61
|
+
var serialHex = serial.toString("hex");
|
|
62
|
+
|
|
63
|
+
// certID = SEQ { AlgId{sha1,NULL}, issuerNameHash OCTET(20), issuerKeyHash OCTET(20), serial INT }
|
|
64
|
+
// (the evaluator only reads the serial from certID; the two hashes are not
|
|
65
|
+
// checked, so fixed 20-byte fillers are fine.)
|
|
66
|
+
var hashAlg = asn1.writeSequence([asn1.writeOid("1.3.14.3.2.26"), asn1.writeNull()]); // sha1
|
|
67
|
+
var nameHash = asn1.writeOctetString(Buffer.alloc(20, 0xaa));
|
|
68
|
+
var keyHash = asn1.writeOctetString(Buffer.alloc(20, 0xbb));
|
|
69
|
+
var certId = asn1.writeSequence([hashAlg, nameHash, keyHash, asn1.writeInteger(serial)]);
|
|
70
|
+
|
|
71
|
+
// certStatus good = [0] IMPLICIT NULL (empty primitive context tag).
|
|
72
|
+
var certStatusGood = asn1.writeContextImplicit(0, Buffer.alloc(0));
|
|
73
|
+
|
|
74
|
+
// SingleResponse = SEQ { certID, certStatus, thisUpdate, [0] EXPLICIT nextUpdate }
|
|
75
|
+
var singleResponseChildren = [
|
|
76
|
+
certId,
|
|
77
|
+
certStatusGood,
|
|
78
|
+
_genTime(opts.thisUpdateMs),
|
|
79
|
+
];
|
|
80
|
+
if (typeof opts.nextUpdateMs === "number") {
|
|
81
|
+
singleResponseChildren.push(asn1.writeContextExplicit(0, _genTime(opts.nextUpdateMs)));
|
|
82
|
+
}
|
|
83
|
+
var singleResponse = asn1.writeSequence(singleResponseChildren);
|
|
84
|
+
|
|
85
|
+
// ResponseData (tbs) = SEQ { responderID [2] EXPLICIT KeyHash, producedAt, responses SEQ-OF }
|
|
86
|
+
var responderId = asn1.writeContextExplicit(2, asn1.writeOctetString(Buffer.alloc(20, 0xcc)));
|
|
87
|
+
var producedAt = _genTime(FIXED_NOW);
|
|
88
|
+
var responses = asn1.writeSequence([singleResponse]);
|
|
89
|
+
var tbs = asn1.writeSequence([responderId, producedAt, responses]);
|
|
90
|
+
|
|
91
|
+
// Sign the tbsResponseData DER (header + value) — exactly the bytes the
|
|
92
|
+
// verifier slices and checks. node emits a DER ECDSA-Sig-Value, which the
|
|
93
|
+
// verifier accepts for OID 1.2.840.10045.4.3.2.
|
|
94
|
+
var sig = nodeCrypto.sign("sha256", tbs, kp.privateKey);
|
|
95
|
+
|
|
96
|
+
var sigAlg = asn1.writeSequence([asn1.writeOid("1.2.840.10045.4.3.2")]); // ecdsa-with-SHA256
|
|
97
|
+
var basic = asn1.writeSequence([tbs, sigAlg, asn1.writeBitString(sig)]);
|
|
98
|
+
|
|
99
|
+
// responseBytes [0] EXPLICIT SEQ { id-pkix-ocsp-basic, OCTET(basic) }
|
|
100
|
+
var responseBytesInner = asn1.writeSequence([
|
|
101
|
+
asn1.writeOid("1.3.6.1.5.5.7.48.1.1"),
|
|
102
|
+
asn1.writeOctetString(basic),
|
|
103
|
+
]);
|
|
104
|
+
var responseBytes = asn1.writeContextExplicit(0, responseBytesInner);
|
|
105
|
+
|
|
106
|
+
// OCSPResponse = SEQ { responseStatus ENUMERATED(0 successful), responseBytes }
|
|
107
|
+
var responseStatus = asn1.writeNode(0x0a, Buffer.from([0]));
|
|
108
|
+
var der = asn1.writeSequence([responseStatus, responseBytes]);
|
|
109
|
+
|
|
110
|
+
return { der: der, issuerPem: issuerPem, serialHex: serialHex };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// A STALE response (nextUpdate well in the past) MUST be rejected — and for
|
|
114
|
+
// the STALE reason, not the misleading "missing thisUpdate" the dead gate
|
|
115
|
+
// produced. RED before the fix: ok:false but errors=["...missing thisUpdate..."].
|
|
116
|
+
function testRejectsStaleResponse() {
|
|
117
|
+
var fx = _buildSignedOcsp({
|
|
118
|
+
thisUpdateMs: FIXED_NOW - 3 * 86400000, // 3 days ago
|
|
119
|
+
nextUpdateMs: FIXED_NOW - 2 * 86400000, // 2 days ago → STALE
|
|
120
|
+
});
|
|
121
|
+
var rv = b.network.tls.ocsp.evaluate(fx.der, {
|
|
122
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
123
|
+
});
|
|
124
|
+
check("stale: rejected (ok=false)", rv.ok === false);
|
|
125
|
+
check("stale: signature still verified (proves we reached the freshness gate)",
|
|
126
|
+
rv.signatureValid === true);
|
|
127
|
+
var errs = (rv.errors || []).join(" ; ");
|
|
128
|
+
check("stale: rejected for the STALE reason, not 'missing thisUpdate'",
|
|
129
|
+
/nextUpdate|stale/i.test(errs) && !/missing thisUpdate/i.test(errs));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// A FRESH response (thisUpdate just past, nextUpdate in the future) MUST be
|
|
133
|
+
// accepted. RED before the fix: rejected as "missing thisUpdate" (the bug
|
|
134
|
+
// cannot distinguish fresh from stale — both wrongly ok:false).
|
|
135
|
+
function testAcceptsFreshResponse() {
|
|
136
|
+
var fx = _buildSignedOcsp({
|
|
137
|
+
thisUpdateMs: FIXED_NOW - 3600000, // 1 hour ago
|
|
138
|
+
nextUpdateMs: FIXED_NOW + 86400000, // +1 day → FRESH
|
|
139
|
+
});
|
|
140
|
+
var rv = b.network.tls.ocsp.evaluate(fx.der, {
|
|
141
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
142
|
+
});
|
|
143
|
+
check("fresh: accepted (ok=true)", rv.ok === true);
|
|
144
|
+
check("fresh: certStatus good", rv.certStatus === "good");
|
|
145
|
+
check("fresh: signature verified", rv.signatureValid === true);
|
|
146
|
+
check("fresh: no errors", Array.isArray(rv.errors) && rv.errors.length === 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// A FUTURE-dated thisUpdate (clock skew / replay) MUST be rejected for the
|
|
150
|
+
// future reason — proves the future-window check is live, not dead.
|
|
151
|
+
function testRejectsFutureThisUpdate() {
|
|
152
|
+
var fx = _buildSignedOcsp({
|
|
153
|
+
thisUpdateMs: FIXED_NOW + 2 * 86400000, // 2 days in the future
|
|
154
|
+
nextUpdateMs: FIXED_NOW + 3 * 86400000,
|
|
155
|
+
});
|
|
156
|
+
var rv = b.network.tls.ocsp.evaluate(fx.der, {
|
|
157
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
158
|
+
});
|
|
159
|
+
check("future: rejected (ok=false)", rv.ok === false);
|
|
160
|
+
check("future: rejected for the FUTURE reason",
|
|
161
|
+
/future/i.test((rv.errors || []).join(" ; ")));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// A fresh response with NO nextUpdate (optional field absent) MUST be
|
|
165
|
+
// accepted — guards the typeof-guard's null→NaN handling of the optional
|
|
166
|
+
// nextUpdate branch.
|
|
167
|
+
function testAcceptsFreshNoNextUpdate() {
|
|
168
|
+
var fx = _buildSignedOcsp({
|
|
169
|
+
thisUpdateMs: FIXED_NOW - 3600000, // 1 hour ago, no nextUpdate
|
|
170
|
+
});
|
|
171
|
+
var rv = b.network.tls.ocsp.evaluate(fx.der, {
|
|
172
|
+
issuerPem: fx.issuerPem, serialHex: fx.serialHex, now: FIXED_NOW,
|
|
173
|
+
});
|
|
174
|
+
check("no-nextUpdate fresh: accepted (ok=true)", rv.ok === true);
|
|
175
|
+
check("no-nextUpdate fresh: certStatus good", rv.certStatus === "good");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function run() {
|
|
179
|
+
testRejectsStaleResponse();
|
|
180
|
+
testAcceptsFreshResponse();
|
|
181
|
+
testRejectsFutureThisUpdate();
|
|
182
|
+
testAcceptsFreshNoNextUpdate();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { run: run };
|
|
186
|
+
|
|
187
|
+
if (require.main === module) {
|
|
188
|
+
run().then(
|
|
189
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
|
|
190
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -45,6 +45,39 @@ function testVersionFallbackWhenNoCommit() {
|
|
|
45
45
|
check("no COMMIT, matching VERSION -> current via version", same.stale === false && same.basis === "version");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function testTimestampDirectionalNewerLocalNotStale() {
|
|
49
|
+
// The CDN-serves-an-older-edge case. publicsuffix.org is CDN-served and
|
|
50
|
+
// different edges can return an OLDER cached copy than the one we vendored.
|
|
51
|
+
// Our bundle is NEWER, so it is NOT stale — but the old exact commit/version
|
|
52
|
+
// inequality flagged it stale (commits differ) and failed the release gate
|
|
53
|
+
// non-deterministically. With a parseable VERSION timestamp on both sides the
|
|
54
|
+
// comparison is directional: only a bundle OLDER than upstream is stale.
|
|
55
|
+
// RED on the old logic (basis "commit", stale true); GREEN now.
|
|
56
|
+
var v = cur._classifyContentCurrency(
|
|
57
|
+
_doc("9186eee", "2026-06-13_21-47-18_UTC"), // upstream edge: older, different commit
|
|
58
|
+
_doc("27a7b5d", "2026-06-22_11-46-12_UTC"), // our bundle: newer
|
|
59
|
+
PSL);
|
|
60
|
+
check("local timestamp NEWER than upstream -> not stale (stale CDN edge)",
|
|
61
|
+
v.stale === false && v.basis === "version-timestamp");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function testTimestampDirectionalOlderLocalStale() {
|
|
65
|
+
// Genuine drift: upstream advanced past our bundle -> stale (gate fires).
|
|
66
|
+
var v = cur._classifyContentCurrency(
|
|
67
|
+
_doc("27a7b5d", "2026-06-22_11-46-12_UTC"), // upstream: newer
|
|
68
|
+
_doc("9186eee", "2026-06-13_21-47-18_UTC"), // our bundle: older
|
|
69
|
+
PSL);
|
|
70
|
+
check("local timestamp OLDER than upstream -> stale", v.stale === true && v.basis === "version-timestamp");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function testTimestampDirectionalEqualNotStale() {
|
|
74
|
+
var v = cur._classifyContentCurrency(
|
|
75
|
+
_doc("27a7b5d", "2026-06-22_11-46-12_UTC"),
|
|
76
|
+
_doc("27a7b5d", "2026-06-22_11-46-12_UTC"),
|
|
77
|
+
PSL);
|
|
78
|
+
check("equal timestamps -> not stale", v.stale === false && v.basis === "version-timestamp");
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
function testNoIdentifierThrows() {
|
|
49
82
|
// Neither side yields a comparable id (e.g. upstream format changed) —
|
|
50
83
|
// must throw so the caller reports registry-error, never a silent pass.
|
|
@@ -66,6 +99,9 @@ function run() {
|
|
|
66
99
|
testCommitMatchIsCurrent();
|
|
67
100
|
testCommitDifferIsStale();
|
|
68
101
|
testVersionFallbackWhenNoCommit();
|
|
102
|
+
testTimestampDirectionalNewerLocalNotStale();
|
|
103
|
+
testTimestampDirectionalOlderLocalStale();
|
|
104
|
+
testTimestampDirectionalEqualNotStale();
|
|
69
105
|
testNoIdentifierThrows();
|
|
70
106
|
testGateConfigLockedIn();
|
|
71
107
|
console.log("[vendor-currency-classify] OK — " + helpers.getChecks() + " checks passed");
|
package/package.json
CHANGED