@luckystack/core 0.2.5 → 0.2.7

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/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  tryCatchSync,
50
50
  validatePagePath,
51
51
  waitForSocket
52
- } from "./chunk-HQX3X5QW.js";
52
+ } from "./chunk-6YKLU3ZR.js";
53
53
  import {
54
54
  DEFAULT_PROJECT_CONFIG,
55
55
  deepMerge,
@@ -96,7 +96,7 @@ import {
96
96
  startSpan,
97
97
  startSpanAcrossTrackers,
98
98
  startSpanHandle
99
- } from "./chunk-V2WKGTZH.js";
99
+ } from "./chunk-OHGJSQZE.js";
100
100
  import {
101
101
  createRegistry
102
102
  } from "./chunk-USQA6QTX.js";
@@ -1246,10 +1246,16 @@ var isLoopbackIp = (ip) => {
1246
1246
  };
1247
1247
  var TOKEN_HASH_LENGTH = 32;
1248
1248
  var deriveTokenBucketId = (token) => createHash2("sha256").update(token).digest("hex").slice(0, TOKEN_HASH_LENGTH);
1249
+ var UNKNOWN_IP_WARN_COOLDOWN_MS = 6e4;
1250
+ var unknownIpLastWarnedAt = 0;
1249
1251
  var resolveClientIp = ({ rawAddress, headers, trustProxy = false, trustedProxyHopCount = 1 }) => {
1250
1252
  const resolved = resolveRaw({ rawAddress, headers, trustProxy, trustedProxyHopCount });
1251
1253
  if (resolved === UNKNOWN_CLIENT_IP) {
1252
- getLogger().warn('rate-limit: client IP unresolved \u2014 keyed into the shared "unknown" bucket', { trustProxy });
1254
+ const now = Date.now();
1255
+ if (now - unknownIpLastWarnedAt >= UNKNOWN_IP_WARN_COOLDOWN_MS) {
1256
+ unknownIpLastWarnedAt = now;
1257
+ getLogger().warn('rate-limit: client IP unresolved \u2014 keyed into the shared "unknown" bucket', { trustProxy });
1258
+ }
1253
1259
  }
1254
1260
  return resolved;
1255
1261
  };
@@ -1359,8 +1365,15 @@ var checkRateLimitInMemory = ({
1359
1365
  const entry = rateLimitStore.get(key);
1360
1366
  if (!entry || entry.resetAt < now) {
1361
1367
  if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {
1362
- const oldest = rateLimitStore.keys().next().value;
1363
- if (oldest !== void 0) rateLimitStore.delete(oldest);
1368
+ let evictKey;
1369
+ for (const [k, e] of rateLimitStore) {
1370
+ if (e.resetAt < now) {
1371
+ evictKey = k;
1372
+ break;
1373
+ }
1374
+ }
1375
+ evictKey ??= rateLimitStore.keys().next().value;
1376
+ if (evictKey !== void 0) rateLimitStore.delete(evictKey);
1364
1377
  }
1365
1378
  rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });
1366
1379
  return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1e3) };
@@ -1508,8 +1521,7 @@ var getRateLimitStatus = async (key, limit) => {
1508
1521
  var clearRateLimit = async (key) => strategyRegistry.get().clear(key);
1509
1522
  var clearAllRateLimits = async () => {
1510
1523
  if (process.env.NODE_ENV === "production") {
1511
- getLogger().warn("[RateLimiter] clearAllRateLimits() called in production \u2014 ignored. Use clearRateLimit(key) for scoped resets.");
1512
- return;
1524
+ throw new Error("[RateLimiter] clearAllRateLimits() is not allowed in production. Use clearRateLimit(key) for scoped resets.");
1513
1525
  }
1514
1526
  return strategyRegistry.get().clearAll();
1515
1527
  };
@@ -1631,17 +1643,40 @@ var parseObjectFields = (typeText) => {
1631
1643
  const indexSignatures = [];
1632
1644
  let part = "";
1633
1645
  let depth = 0;
1634
- for (const char of inner) {
1635
- if (char === "{" || char === "[" || char === "(" || char === "<") depth += 1;
1636
- if (char === "}" || char === "]" || char === ")" || char === ">") depth -= 1;
1637
- if (char === ";" && depth === 0) {
1638
- const trimmed = part.trim();
1639
- if (trimmed) {
1640
- parseFieldOrIndex(trimmed, fields, indexSignatures);
1646
+ let inString = null;
1647
+ const codePoints = [...inner];
1648
+ let i = 0;
1649
+ while (i < codePoints.length) {
1650
+ const char = codePoints[i] ?? "";
1651
+ i += 1;
1652
+ if (inString && char === "\\") {
1653
+ part += char;
1654
+ const next = codePoints[i] ?? "";
1655
+ part += next;
1656
+ i += 1;
1657
+ continue;
1658
+ }
1659
+ if (char === '"' || char === "'") {
1660
+ if (inString === char) {
1661
+ inString = null;
1662
+ } else {
1663
+ inString ??= char;
1641
1664
  }
1642
- part = "";
1665
+ part += char;
1643
1666
  continue;
1644
1667
  }
1668
+ if (!inString) {
1669
+ if (char === "{" || char === "[" || char === "(" || char === "<") depth += 1;
1670
+ if (char === "}" || char === "]" || char === ")" || char === ">") depth -= 1;
1671
+ if (char === ";" && depth === 0) {
1672
+ const trimmed = part.trim();
1673
+ if (trimmed) {
1674
+ parseFieldOrIndex(trimmed, fields, indexSignatures);
1675
+ }
1676
+ part = "";
1677
+ continue;
1678
+ }
1679
+ }
1645
1680
  part += char;
1646
1681
  }
1647
1682
  const final = part.trim();
@@ -1905,16 +1940,17 @@ var allowedOrigin = (origin) => {
1905
1940
  }
1906
1941
  }
1907
1942
  const debugAllowedOrigins = typeof configured === "function" ? ["<dynamic-resolver>", location] : [location, ...configured].map((value) => normalizeOrigin({ value, secure })).filter(Boolean);
1943
+ const safeOrigin = origin.slice(0, 512).replaceAll(/[^ -~]/g, "");
1908
1944
  if (getProjectConfig().logging.devLogs) {
1909
1945
  getLogger().warn("cors: origin not allowed", {
1910
- origin,
1946
+ origin: safeOrigin,
1911
1947
  normalizedOrigin,
1912
1948
  allowedOrigins: debugAllowedOrigins,
1913
1949
  allowLocalhost: cors.allowLocalhost
1914
1950
  });
1915
1951
  }
1916
1952
  void dispatchHook("corsRejected", {
1917
- origin,
1953
+ origin: safeOrigin,
1918
1954
  normalizedOrigin,
1919
1955
  allowedOrigins: debugAllowedOrigins,
1920
1956
  allowLocalhost: cors.allowLocalhost
@@ -1994,11 +2030,13 @@ var consumeOneTimeToken = async (namespace, token) => {
1994
2030
  if (!token || typeof token !== "string") return null;
1995
2031
  const key = tokenKey(namespace, token);
1996
2032
  const txResult = await redis_default.multi().get(key).del(key).exec();
1997
- if (!txResult || txResult.length === 0) return null;
2033
+ if (!txResult || txResult.length < 2) return null;
1998
2034
  const first = txResult[0];
1999
2035
  if (!first) return null;
2000
2036
  const [getErr, value] = first;
2001
2037
  if (getErr) return null;
2038
+ const second = txResult[1];
2039
+ if (!second || second[0]) return null;
2002
2040
  return typeof value === "string" && value.length > 0 ? value : null;
2003
2041
  };
2004
2042
  var consumeOneTimeTokenJson = async (namespace, token) => {