@zudojs/security 1.0.0 → 1.1.0

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.
Files changed (38) hide show
  1. package/README.md +56 -5
  2. package/dist/body/body.core.d.ts +6 -0
  3. package/dist/body/body.core.js +24 -4
  4. package/dist/body/body.guard.d.ts +19 -0
  5. package/dist/body/body.guard.js +26 -0
  6. package/dist/cookie/cookie.core.d.ts +10 -6
  7. package/dist/cookie/cookie.core.js +22 -20
  8. package/dist/cookie/cookie.sensitive.d.ts +27 -0
  9. package/dist/cookie/cookie.sensitive.js +61 -0
  10. package/dist/cookie/index.d.ts +1 -0
  11. package/dist/cookie/index.js +1 -0
  12. package/dist/cors/cors.core.js +2 -1
  13. package/dist/csrf/csrf.core.d.ts +3 -1
  14. package/dist/csrf/csrf.core.js +19 -5
  15. package/dist/headers/headers.core.js +2 -1
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +3 -3
  18. package/dist/input/input.core.js +10 -1
  19. package/dist/input/input.decode.d.ts +25 -0
  20. package/dist/input/input.decode.js +72 -0
  21. package/dist/rateLimit/index.d.ts +9 -2
  22. package/dist/rateLimit/index.js +6 -1
  23. package/dist/rateLimit/rateLimit.clientIp.d.ts +40 -0
  24. package/dist/rateLimit/rateLimit.clientIp.js +63 -0
  25. package/dist/rateLimit/rateLimit.clientKey.d.ts +51 -0
  26. package/dist/rateLimit/rateLimit.clientKey.js +104 -0
  27. package/dist/rateLimit/rateLimit.core.d.ts +5 -30
  28. package/dist/rateLimit/rateLimit.core.js +37 -84
  29. package/dist/rateLimit/rateLimit.namespace.d.ts +2 -1
  30. package/dist/rateLimit/rateLimit.namespace.js +2 -1
  31. package/dist/types/security.type.d.ts +1 -1
  32. package/dist/types/security.type.js +3 -0
  33. package/dist/url/index.d.ts +1 -0
  34. package/dist/url/index.js +1 -0
  35. package/dist/url/url.core.js +12 -27
  36. package/dist/url/url.ipv6.d.ts +36 -0
  37. package/dist/url/url.ipv6.js +88 -0
  38. package/package.json +7 -3
@@ -3,21 +3,30 @@
3
3
  *
4
4
  * Implements sliding window rate limiting to prevent abuse.
5
5
  */
6
+ import { createIpKeyGenerator, ipRateLimitKey } from "./rateLimit.clientKey.js";
6
7
  /** Default window: 1 minute. */
7
8
  const DEFAULT_WINDOW_MS = 60_000;
8
9
  /** Default rate limit message. */
9
10
  const DEFAULT_MESSAGE = "Too many requests";
10
11
  /** Default cap on tracked keys before least-recently-seen eviction. */
11
12
  const DEFAULT_MAX_KEYS = 100_000;
13
+ /** Largest delay `setInterval` honours without overflowing to 1 ms. */
14
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
12
15
  /**
13
16
  * Default key generator using IP address.
14
17
  *
18
+ * The port is stripped, IPv4-mapped IPv6 is keyed as IPv4, and IPv6 is
19
+ * bucketed by /64 (see {@link createIpKeyGenerator} for another prefix).
20
+ *
15
21
  * @param request - The rate limit request.
16
22
  * @returns The rate limit key.
23
+ * @throws {ConfigurationError} when `request.ip` is missing or not an IP
24
+ * address; requests used to share a single `"unknown"` bucket.
17
25
  */
18
26
  export function defaultKeyGenerator(request) {
19
- return request.ip ?? "unknown";
27
+ return ipKeyGenerator(request);
20
28
  }
29
+ const ipKeyGenerator = createIpKeyGenerator();
21
30
  /**
22
31
  * Default handler when rate limit is exceeded.
23
32
  *
@@ -79,6 +88,8 @@ export function createRateLimiter(config) {
79
88
  }
80
89
  const store = new Map();
81
90
  const keyGenerator = config.keyGenerator ?? defaultKeyGenerator;
91
+ /** Lets `reset`/`getCount` take a raw address under the default keys. */
92
+ const storeKey = (key) => config.keyGenerator || store.has(key) ? key : (ipRateLimitKey(key) ?? key);
82
93
  // `config.message` was declared and documented but never read: the default
83
94
  // handler always emitted the built-in string.
84
95
  const message = config.message ?? DEFAULT_MESSAGE;
@@ -88,8 +99,11 @@ export function createRateLimiter(config) {
88
99
  });
89
100
  const skip = config.skip;
90
101
  const maxKeys = config.maxKeys ?? DEFAULT_MAX_KEYS;
91
- // Cleanup old entries periodically. Bounded so a very short window does not
92
- // schedule a near-continuous timer.
102
+ // Cleanup old entries periodically. Bounded below so a very short window
103
+ // does not schedule a near-continuous timer, and above because Node stores
104
+ // timer delays as a signed 32-bit integer: a delay past 2^31 - 1 ms (about
105
+ // 24.8 days) is silently replaced with 1 ms, so a month-long window used to
106
+ // sweep the whole store a thousand times a second.
93
107
  const cleanupInterval = setInterval(() => {
94
108
  const cutoff = Date.now() - config.windowMs;
95
109
  for (const [key, entry] of store) {
@@ -97,7 +111,7 @@ export function createRateLimiter(config) {
97
111
  store.delete(key);
98
112
  }
99
113
  }
100
- }, Math.max(config.windowMs, 1_000));
114
+ }, Math.min(Math.max(config.windowMs, 1_000), MAX_TIMER_DELAY_MS));
101
115
  // Allow cleanup to not keep process alive
102
116
  if (cleanupInterval.unref) {
103
117
  cleanupInterval.unref();
@@ -107,16 +121,20 @@ export function createRateLimiter(config) {
107
121
  *
108
122
  * Without this, a caller rotating the key (a spoofed forwarding header, a
109
123
  * per-request identifier) grows the map without limit between sweeps.
124
+ *
125
+ * The store is kept in recency order — `check` re-inserts an entry on every
126
+ * hit — so the oldest key is always the first one iterated and eviction is
127
+ * O(1). It used to copy and sort the whole map on every new key past the
128
+ * cap, which turned the defence against key rotation into an O(n log n)
129
+ * cost per rotated request: the attack it was meant to bound became the
130
+ * cheapest way to burn the CPU.
110
131
  */
111
132
  function evictIfNeeded() {
112
- if (store.size <= maxKeys)
113
- return;
114
- const entries = [...store.entries()].sort((a, b) => a[1].lastSeen - b[1].lastSeen);
115
- const excess = store.size - maxKeys;
116
- for (let i = 0; i < excess; i++) {
117
- const entry = entries[i];
118
- if (entry)
119
- store.delete(entry[0]);
133
+ while (store.size > maxKeys) {
134
+ const oldest = store.keys().next();
135
+ if (oldest.done)
136
+ break;
137
+ store.delete(oldest.value);
120
138
  }
121
139
  }
122
140
  /**
@@ -141,6 +159,11 @@ export function createRateLimiter(config) {
141
159
  store.set(key, entry);
142
160
  evictIfNeeded();
143
161
  }
162
+ else {
163
+ // Move to the most-recent end so eviction order stays least-recent-first.
164
+ store.delete(key);
165
+ store.set(key, entry);
166
+ }
144
167
  // Prune everything that has slid out of the window.
145
168
  const timestamps = entry.timestamps.filter((t) => t > windowStart);
146
169
  entry.lastSeen = now;
@@ -177,7 +200,7 @@ export function createRateLimiter(config) {
177
200
  * Resets the rate limit for a specific key.
178
201
  */
179
202
  function reset(key) {
180
- store.delete(key);
203
+ store.delete(storeKey(key));
181
204
  }
182
205
  /**
183
206
  * Clears all rate limit data.
@@ -189,7 +212,7 @@ export function createRateLimiter(config) {
189
212
  * Gets the current count for a key.
190
213
  */
191
214
  function getCount(key) {
192
- const entry = store.get(key);
215
+ const entry = store.get(storeKey(key));
193
216
  if (!entry)
194
217
  return 0;
195
218
  const windowStart = Date.now() - config.windowMs;
@@ -215,74 +238,4 @@ export function createRateLimiter(config) {
215
238
  },
216
239
  };
217
240
  }
218
- /**
219
- * Extracts the client IP from request headers.
220
- *
221
- * **Forwarding headers are not trusted by default.** Any client can send
222
- * `X-Forwarded-For`, so taking its leftmost entry — the historical behaviour —
223
- * hands the caller control of their own rate-limit bucket, and rotating it
224
- * defeats the limiter entirely. Pass `trustProxy` set to the number of proxies
225
- * you actually run, together with the socket's `remoteAddress`.
226
- *
227
- * @param headers - Request headers.
228
- * @param options - Proxy trust configuration.
229
- * @returns The client IP address, or "unknown".
230
- */
231
- export function extractClientIp(headers, options) {
232
- const trustProxy = options?.trustProxy ?? 0;
233
- const fallback = options?.remoteAddress ?? "unknown";
234
- if (trustProxy <= 0) {
235
- return fallback;
236
- }
237
- const raw = lookupHeader(headers, "x-forwarded-for");
238
- if (raw !== undefined) {
239
- const chain = raw
240
- .split(",")
241
- .map((entry) => entry.trim())
242
- .filter((entry) => entry.length > 0);
243
- // Walk in from the right: index 0 from the end is the address our own
244
- // outermost proxy observed, and each additional trusted hop steps left.
245
- const index = chain.length - trustProxy;
246
- const candidate = chain[Math.max(0, index)];
247
- if (candidate && isPlausibleIp(candidate)) {
248
- return candidate;
249
- }
250
- }
251
- const realIp = lookupHeader(headers, "x-real-ip");
252
- if (realIp !== undefined && isPlausibleIp(realIp.trim())) {
253
- return realIp.trim();
254
- }
255
- return fallback;
256
- }
257
- /** Case-insensitive header lookup that flattens repeated fields. */
258
- function lookupHeader(headers, name) {
259
- for (const key of Object.keys(headers)) {
260
- if (key.toLowerCase() !== name)
261
- continue;
262
- const value = headers[key];
263
- if (typeof value === "string")
264
- return value;
265
- if (Array.isArray(value) && value.length > 0)
266
- return value.join(",");
267
- }
268
- return undefined;
269
- }
270
- /**
271
- * Rejects values that are not addresses at all.
272
- *
273
- * A forwarding header is text, and a hostname or arbitrary string in it would
274
- * otherwise become a rate-limit key of the attacker's choosing.
275
- */
276
- function isPlausibleIp(value) {
277
- const host = value.startsWith("[")
278
- ? value.slice(1, value.indexOf("]") === -1 ? undefined : value.indexOf("]"))
279
- : value.split(":").length > 2
280
- ? value
281
- : (value.split(":")[0] ?? value);
282
- if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) {
283
- return host.split(".").every((octet) => Number(octet) <= 255);
284
- }
285
- // Any hex-and-colon string is accepted as an IPv6 candidate.
286
- return /^[0-9a-fA-F:]+$/.test(host) && host.includes(":");
287
- }
288
241
  //# sourceMappingURL=rateLimit.core.js.map
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Convenience namespace for rate limiting utilities.
5
5
  */
6
- import { defaultKeyGenerator, defaultHandler, retryAfterSeconds, createRateLimiter, extractClientIp } from "./rateLimit.core.js";
6
+ import { defaultKeyGenerator, defaultHandler, retryAfterSeconds, createRateLimiter } from "./rateLimit.core.js";
7
+ import { extractClientIp } from "./rateLimit.clientIp.js";
7
8
  export declare const rateLimit: {
8
9
  defaultKeyGenerator: typeof defaultKeyGenerator;
9
10
  defaultHandler: typeof defaultHandler;
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Convenience namespace for rate limiting utilities.
5
5
  */
6
- import { defaultKeyGenerator, defaultHandler, retryAfterSeconds, createRateLimiter, extractClientIp, } from "./rateLimit.core.js";
6
+ import { defaultKeyGenerator, defaultHandler, retryAfterSeconds, createRateLimiter, } from "./rateLimit.core.js";
7
+ import { extractClientIp } from "./rateLimit.clientIp.js";
7
8
  export const rateLimit = {
8
9
  defaultKeyGenerator,
9
10
  defaultHandler,
@@ -207,7 +207,7 @@ export declare const PROTOTYPE_POLLUTION_KEYS: readonly ["__proto__", "construct
207
207
  *
208
208
  * Like {@link XSS_PATTERNS}, none carries the `g` flag — see the note there.
209
209
  */
210
- export declare const SQL_INJECTION_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp];
210
+ export declare const SQL_INJECTION_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
211
211
  /**
212
212
  * Common XSS patterns.
213
213
  *
@@ -22,6 +22,9 @@ export const SQL_INJECTION_PATTERNS = [
22
22
  /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|EXECUTE|UNION|FETCH|DECLARE|TRUNCATE|COMMENT|ALTER)\b)/i,
23
23
  /(--|#|\/\*|\*\/)/,
24
24
  /('\s*(OR|AND)\s*')/i,
25
+ /'\s*(OR|AND)\b/i,
26
+ /'\s*\|\|/,
27
+ /\b(PG_SLEEP|SLEEP|BENCHMARK)\s*\(|\bWAITFOR\s+DELAY\b/i,
25
28
  /(;\s*(DROP|DELETE|INSERT|UPDATE))/i,
26
29
  ];
27
30
  /**
@@ -3,4 +3,5 @@
3
3
  */
4
4
  export { validateUrl, normalizePath, validateRequestTarget, isSafeUrl, isPrivateHostname, containsTraversal, fullyDecodeUri, } from "./url.core.js";
5
5
  export type { RequestTargetConfig } from "./url.core.js";
6
+ export { expandIpv6, embeddedIpv4, isNonPublicIpv6Range, } from "./url.ipv6.js";
6
7
  //# sourceMappingURL=index.d.ts.map
package/dist/url/index.js CHANGED
@@ -2,4 +2,5 @@
2
2
  * @zudojs/security — URL Validation Barrel
3
3
  */
4
4
  export { validateUrl, normalizePath, validateRequestTarget, isSafeUrl, isPrivateHostname, containsTraversal, fullyDecodeUri, } from "./url.core.js";
5
+ export { expandIpv6, embeddedIpv4, isNonPublicIpv6Range, } from "./url.ipv6.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -4,6 +4,7 @@
4
4
  * Validates and normalizes URLs, prevents path traversal attacks,
5
5
  * and ensures request targets are safe.
6
6
  */
7
+ import { embeddedIpv4, expandIpv6, isNonPublicIpv6Range, } from "./url.ipv6.js";
7
8
  /** Default maximum URL length. */
8
9
  const DEFAULT_MAX_URL_LENGTH = 2048;
9
10
  /** Default allowed protocols. */
@@ -301,36 +302,20 @@ function isPrivateIpv4(octets) {
301
302
  /**
302
303
  * True when an IPv6 hostname (already stripped of brackets) is private.
303
304
  *
304
- * Also unwraps IPv4-mapped and IPv4-compatible forms, so `::ffff:127.0.0.1`
305
- * is recognised as loopback rather than treated as an opaque v6 address.
305
+ * Every form that embeds an IPv4 address — IPv4-compatible `::a.b.c.d`,
306
+ * mapped `::ffff:a.b.c.d`, translated `::ffff:0:a.b.c.d`, NAT64
307
+ * `64:ff9b::a.b.c.d` and 6to4 `2002::/16` — is judged as that IPv4
308
+ * address, whichever spelling it arrives in (WHATWG serialises
309
+ * `[::127.0.0.1]` as `[::7f00:1]`). An unparseable literal fails closed.
306
310
  */
307
311
  function isPrivateIpv6(hostname) {
308
- const host = hostname.toLowerCase();
309
- if (host === "::1" || host === "::" || host === "::0")
312
+ const groups = expandIpv6(hostname);
313
+ if (!groups)
310
314
  return true;
311
- // IPv4-mapped (::ffff:127.0.0.1) and IPv4-compatible (::127.0.0.1)
312
- const mapped = /^::(?:ffff:)?(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
313
- if (mapped?.[1]) {
314
- const octets = parseIpv4(mapped[1]);
315
- return octets ? isPrivateIpv4(octets) : true;
316
- }
317
- // Hex-form IPv4-mapped: ::ffff:7f00:1
318
- const hexMapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
319
- if (hexMapped?.[1] && hexMapped[2]) {
320
- const high = parseInt(hexMapped[1], 16);
321
- const low = parseInt(hexMapped[2], 16);
322
- const octets = [high >> 8, high & 0xff, low >> 8, low & 0xff];
323
- return isPrivateIpv4(octets);
324
- }
325
- const firstGroup = host.split(":")[0] ?? "";
326
- const leading = parseInt(firstGroup.padEnd(4, "0"), 16);
327
- // fc00::/7 unique local
328
- if ((leading & 0xfe00) === 0xfc00)
329
- return true;
330
- // fe80::/10 link-local
331
- if ((leading & 0xffc0) === 0xfe80)
332
- return true;
333
- return false;
315
+ const embedded = embeddedIpv4(groups);
316
+ if (embedded)
317
+ return isPrivateIpv4(embedded);
318
+ return isNonPublicIpv6Range(groups);
334
319
  }
335
320
  /**
336
321
  * Hostnames that resolve inside the local network or to a metadata service.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @zudojs/security — IPv6 literal parsing and embedded-IPv4 extraction.
3
+ */
4
+ /**
5
+ * Expands an IPv6 address into its eight 16-bit groups.
6
+ *
7
+ * Accepts the compressed (`::`), dotted-tail (`::ffff:1.2.3.4`) and zoned
8
+ * (`fe80::1%eth0`) forms.
9
+ *
10
+ * @returns The groups, or `undefined` for a non-IPv6 input.
11
+ */
12
+ export declare function expandIpv6(address: string): number[] | undefined;
13
+ /**
14
+ * The IPv4 address an IPv6 address stands for, when it is one of the
15
+ * embedding forms, so that it can be judged as that IPv4 address.
16
+ *
17
+ * - `::a.b.c.d` (IPv4-compatible, `::/96`, which also covers `::` and `::1`)
18
+ * - `::ffff:a.b.c.d` (IPv4-mapped, `::ffff:0:0/96`)
19
+ * - `::ffff:0:a.b.c.d` (IPv4-translated, `::ffff:0:0:0/96`)
20
+ * - `64:ff9b::a.b.c.d` (NAT64 well-known prefix, `64:ff9b::/96`)
21
+ * - `2002:aabb:ccdd::` (6to4, `2002::/16`)
22
+ *
23
+ * WHATWG URL parsing rewrites `[::127.0.0.1]` to `[::7f00:1]`, so matching
24
+ * on the dotted spelling (as the old regexes did) missed every one of these.
25
+ *
26
+ * @returns The four octets, or `undefined` when nothing is embedded.
27
+ */
28
+ export declare function embeddedIpv4(groups: readonly number[]): number[] | undefined;
29
+ /**
30
+ * True for IPv6 ranges that are never a public unicast destination:
31
+ * unique-local `fc00::/7`, link-local `fe80::/10`, deprecated site-local
32
+ * `fec0::/10`, multicast `ff00::/8`, and the local-use NAT64 prefix
33
+ * `64:ff9b:1::/48`, whose embedded address depends on local configuration.
34
+ */
35
+ export declare function isNonPublicIpv6Range(groups: readonly number[]): boolean;
36
+ //# sourceMappingURL=url.ipv6.d.ts.map
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @zudojs/security — IPv6 literal parsing and embedded-IPv4 extraction.
3
+ */
4
+ import { isIPv6 } from "node:net";
5
+ /**
6
+ * Expands an IPv6 address into its eight 16-bit groups.
7
+ *
8
+ * Accepts the compressed (`::`), dotted-tail (`::ffff:1.2.3.4`) and zoned
9
+ * (`fe80::1%eth0`) forms.
10
+ *
11
+ * @returns The groups, or `undefined` for a non-IPv6 input.
12
+ */
13
+ export function expandIpv6(address) {
14
+ let text = address.toLowerCase();
15
+ const zone = text.indexOf("%");
16
+ if (zone !== -1)
17
+ text = text.slice(0, zone);
18
+ if (!isIPv6(text))
19
+ return undefined;
20
+ const lastColon = text.lastIndexOf(":");
21
+ const tail = text.slice(lastColon + 1);
22
+ if (tail.includes(".")) {
23
+ const o = tail.split(".").map(Number);
24
+ const hi = ((o[0] << 8) | o[1]).toString(16);
25
+ const lo = ((o[2] << 8) | o[3]).toString(16);
26
+ text = `${text.slice(0, lastColon + 1)}${hi}:${lo}`;
27
+ }
28
+ const [head = "", rest] = text.split("::");
29
+ const left = head === "" ? [] : head.split(":");
30
+ const right = rest === undefined || rest === "" ? [] : rest.split(":");
31
+ const fill = rest === undefined
32
+ ? []
33
+ : new Array(8 - left.length - right.length).fill("0");
34
+ return [...left, ...fill, ...right].map((group) => parseInt(group, 16));
35
+ }
36
+ function octetsOf(high, low) {
37
+ return [high >> 8, high & 0xff, low >> 8, low & 0xff];
38
+ }
39
+ /**
40
+ * The IPv4 address an IPv6 address stands for, when it is one of the
41
+ * embedding forms, so that it can be judged as that IPv4 address.
42
+ *
43
+ * - `::a.b.c.d` (IPv4-compatible, `::/96`, which also covers `::` and `::1`)
44
+ * - `::ffff:a.b.c.d` (IPv4-mapped, `::ffff:0:0/96`)
45
+ * - `::ffff:0:a.b.c.d` (IPv4-translated, `::ffff:0:0:0/96`)
46
+ * - `64:ff9b::a.b.c.d` (NAT64 well-known prefix, `64:ff9b::/96`)
47
+ * - `2002:aabb:ccdd::` (6to4, `2002::/16`)
48
+ *
49
+ * WHATWG URL parsing rewrites `[::127.0.0.1]` to `[::7f00:1]`, so matching
50
+ * on the dotted spelling (as the old regexes did) missed every one of these.
51
+ *
52
+ * @returns The four octets, or `undefined` when nothing is embedded.
53
+ */
54
+ export function embeddedIpv4(groups) {
55
+ const [g0, g1, g2, g3, g4, g5, g6 = 0, g7 = 0] = groups;
56
+ const zeroTo = (end) => groups.slice(0, end).every((group) => group === 0);
57
+ if (zeroTo(6))
58
+ return octetsOf(g6, g7);
59
+ if (zeroTo(5) && g5 === 0xffff)
60
+ return octetsOf(g6, g7);
61
+ if (zeroTo(4) && g4 === 0xffff && g5 === 0)
62
+ return octetsOf(g6, g7);
63
+ if (g0 === 0x64 && g1 === 0xff9b && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
64
+ return octetsOf(g6, g7);
65
+ }
66
+ if (g0 === 0x2002)
67
+ return octetsOf(g1 ?? 0, g2 ?? 0);
68
+ return undefined;
69
+ }
70
+ /**
71
+ * True for IPv6 ranges that are never a public unicast destination:
72
+ * unique-local `fc00::/7`, link-local `fe80::/10`, deprecated site-local
73
+ * `fec0::/10`, multicast `ff00::/8`, and the local-use NAT64 prefix
74
+ * `64:ff9b:1::/48`, whose embedded address depends on local configuration.
75
+ */
76
+ export function isNonPublicIpv6Range(groups) {
77
+ const g0 = groups[0] ?? 0;
78
+ if ((g0 & 0xfe00) === 0xfc00)
79
+ return true;
80
+ if ((g0 & 0xffc0) === 0xfe80)
81
+ return true;
82
+ if ((g0 & 0xffc0) === 0xfec0)
83
+ return true;
84
+ if ((g0 & 0xff00) === 0xff00)
85
+ return true;
86
+ return g0 === 0x64 && groups[1] === 0xff9b && groups[2] === 1;
87
+ }
88
+ //# sourceMappingURL=url.ipv6.js.map
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@zudojs/security",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Security primitives for input validation, header security, CORS, CSRF protection, rate limiting, and security headers.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "module": "./dist/index.js",
@@ -20,8 +24,8 @@
20
24
  "!dist/.tsbuildinfo"
21
25
  ],
22
26
  "dependencies": {
23
- "@zudojs/errors": "1.0.0",
24
- "@zudojs/constants": "1.0.0"
27
+ "@zudojs/errors": "1.1.0",
28
+ "@zudojs/constants": "1.1.0"
25
29
  },
26
30
  "devDependencies": {
27
31
  "typescript": "7.0.2",