@blamejs/blamejs-shop 0.4.92 → 0.4.93
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/asset-manifest.json +1 -1
- package/lib/security-middleware.js +21 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.4.x
|
|
10
10
|
|
|
11
|
+
- v0.4.93 (2026-06-25) — **Per-IP rate limits now key an IPv6 client by its /64 prefix, closing an address-rotation bypass of the login, checkout, and webhook throttles.** The request-throttling layer derived its per-client key from the full client IP address. For an IPv4 client that is one host, but an IPv6 end-site is allocated an entire /64 prefix (RFC 6177 / RFC 4291) and can freely rotate the low 64 bits, presenting a fresh address — and therefore a brand-new rate-limit bucket — on every request. That let a single IPv6 site walk straight through every per-IP ceiling: the global request limiter, the tight per-IP limiter on login / register / passkey / checkout, the PayPal webhook verification budget, and the per-IP captcha budget. The client key now collapses an IPv6 address to its routing-significant /64 (an IPv4 address is still keyed verbatim, and an IPv4-mapped IPv6 keys as its dotted host), so one end-site shares one bucket while distinct sites stay distinct. A value that is not a parseable IP falls back to itself, so two unidentifiable clients never collapse into one shared bucket. No configuration changes. **Fixed:** *IPv6 clients can no longer rotate addresses to bypass per-IP rate limits* — Every per-IP throttle — the global request limiter, the tight limiter on authentication and checkout routes, the PayPal webhook verification budget, and the captcha budget — keyed off the full client IP. An IPv6 client controls a whole /64 and could rotate the low 64 bits to mint an unlimited supply of fresh buckets, defeating each ceiling. The per-client key now masks an IPv6 address to its /64 prefix (IPv4 is unchanged), so a single end-site is held to one bucket. The fix lives in the client-key derivation itself, because the framework's rate-limit key mode is bypassed whenever a custom key function is supplied — as every limiter here does.
|
|
12
|
+
|
|
11
13
|
- v0.4.92 (2026-06-25) — **Refresh the vendored framework to 0.15.23 — a state transition whose entry hook throws is now always recorded in the audit trail.** Updates the vendored blamejs framework from 0.15.20 to 0.15.23. The behavioural change operators will notice is in the order, return, redemption, and dropship state machines, which compose the framework's finite-state-machine primitive: previously, if a destination state's entry hook threw, the state change committed but no audit record was written — leaving a privileged transition unaudited. The framework now always records the transition (stamped with a failure outcome and the hook error) and still re-raises the error, so a state change can never be silently unaudited. The refresh also carries upstream correctness and hardening work in primitives the storefront does not currently reach (a websocket-client reconnect fix, a cross-platform path-containment fix), plus additive primitives the application will adopt in follow-up changes. No configuration changes. **Changed:** *State transitions are always audited, even when an entry hook fails* — The order, return, redemption, and dropship-forwarding state machines record every transition on the framework's tamper-evident audit chain. A transition whose destination entry hook threw previously committed the state change without emitting that record. The refreshed framework always writes the audit record — stamped with a failure outcome and the entry-hook error — and still re-raises the error to the caller, so a privileged state change is never left unaudited.
|
|
12
14
|
|
|
13
15
|
- v0.4.91 (2026-06-24) — **A gift-card refund is now linked to the order it reverses, so an order's gift-card settlement history is complete.** When a refund returns money to a gift card (a cancelled or refunded order that had been paid with the card), the ledger credit now records the order it reverses in the structured order field, not only as a free-text reference. Previously a query for a given order's gift-card movements returned the debits that spent the card but not the refund credits that returned money to it, so the order's gift-card settlement history read as one-sided. The refund credit now carries the order link, so the order's gift-card history shows both the spend and the matching refund. No configuration changes, and existing gift-card behavior — balances, overdraft refusal, and the per-card tamper-evident hash chain — is unchanged. **Fixed:** *Refund-to-gift-card credits carry the order they reverse* — giftCardLedger.credit now accepts an optional order_id, and the order-cancellation/refund path that returns a redeemed amount to a gift card passes the order it reverses. The ledger's transactionsForOrder(order_id) lookup keys on that structured field, so it now returns the refund credits flowing back to a card alongside the debits that drained it for the order — previously the credits were keyed only by a free-text reference and were absent from the lookup. Each refund credit still participates in the per-card hash chain (the order link is covered by the row hash), and credits with no associated order (promotional or manual top-ups) are unaffected.
|
package/lib/asset-manifest.json
CHANGED
|
@@ -283,15 +283,33 @@ var PUBLIC_ORIGINS = (process.env.SHOP_PUBLIC_ORIGINS || "https://blamejs.shop")
|
|
|
283
283
|
* non-empty string so two un-identifiable clients never collapse into
|
|
284
284
|
* the same bucket as a real IP — request-shape reader, returns a
|
|
285
285
|
* default, never throws.
|
|
286
|
+
*
|
|
287
|
+
* The resolved address is collapsed to its rate-limit-significant key via
|
|
288
|
+
* `b.requestHelpers.ipKey`: an IPv4 host is kept verbatim, but an IPv6
|
|
289
|
+
* client is masked to its /64 prefix. A single end-site is allocated a
|
|
290
|
+
* whole IPv6 /64 (RFC 6177 / RFC 4291) and freely rotates the low 64 bits,
|
|
291
|
+
* so keying on the full 128-bit address would let one site present a fresh
|
|
292
|
+
* source on every request and mint unlimited buckets — walking every
|
|
293
|
+
* per-IP limiter (and the captcha-IP budget) that keys off this value.
|
|
294
|
+
* Keying on the /64 closes that while still distinguishing real end-sites.
|
|
286
295
|
*/
|
|
287
296
|
function clientKey(req) {
|
|
288
297
|
var headers = (req && req.headers) || {};
|
|
289
298
|
var cf = headers["cf-connecting-ip"];
|
|
290
|
-
if (typeof cf === "string" && cf.length > 0) return cf.trim();
|
|
299
|
+
if (typeof cf === "string" && cf.length > 0) return _ipBucket(cf.trim());
|
|
291
300
|
var real = headers["x-real-ip"];
|
|
292
|
-
if (typeof real === "string" && real.length > 0) return real.trim();
|
|
301
|
+
if (typeof real === "string" && real.length > 0) return _ipBucket(real.trim());
|
|
293
302
|
var sock = b.requestHelpers.clientIp(req);
|
|
294
|
-
return sock
|
|
303
|
+
return sock ? _ipBucket(sock) : "unknown";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Collapse one resolved client IP to its per-IP bucket key. ipKey returns
|
|
307
|
+
// "" for a value that isn't a parseable IP (a malformed header, "unknown"),
|
|
308
|
+
// so we fall back to the original non-empty string — keeping two
|
|
309
|
+
// un-identifiable clients in distinct buckets rather than collapsing every
|
|
310
|
+
// garbage value into one shared bucket.
|
|
311
|
+
function _ipBucket(ip) {
|
|
312
|
+
return b.requestHelpers.ipKey(ip, { ipv6Bits: 64 }) || ip;
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
/**
|
package/package.json
CHANGED