@zudojs/security 1.3.3 → 1.3.4
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/README.md +4 -1
- package/dist/body/body.core.d.ts +0 -1
- package/dist/body/body.core.js +0 -1
- package/dist/body/body.guard.d.ts +0 -1
- package/dist/body/body.guard.js +0 -1
- package/dist/body/index.d.ts +0 -1
- package/dist/body/index.js +0 -1
- package/dist/cookie/cookie.attribute.d.ts +0 -1
- package/dist/cookie/cookie.attribute.js +0 -1
- package/dist/cookie/cookie.core.d.ts +0 -1
- package/dist/cookie/cookie.core.js +0 -1
- package/dist/cookie/cookie.sensitive.d.ts +0 -1
- package/dist/cookie/cookie.sensitive.js +0 -1
- package/dist/cookie/index.d.ts +0 -1
- package/dist/cookie/index.js +0 -1
- package/dist/cors/cors.core.d.ts +0 -1
- package/dist/cors/cors.core.js +0 -1
- package/dist/cors/cors.namespace.d.ts +0 -1
- package/dist/cors/cors.namespace.js +0 -1
- package/dist/cors/index.d.ts +0 -1
- package/dist/cors/index.js +0 -1
- package/dist/csrf/csrf.core.d.ts +0 -1
- package/dist/csrf/csrf.core.js +0 -1
- package/dist/csrf/index.d.ts +0 -1
- package/dist/csrf/index.js +0 -1
- package/dist/header/header.core.d.ts +0 -1
- package/dist/header/header.core.js +0 -1
- package/dist/header/index.d.ts +0 -1
- package/dist/header/index.js +0 -1
- package/dist/headers/headers.core.d.ts +0 -1
- package/dist/headers/headers.core.js +0 -1
- package/dist/headers/index.d.ts +0 -1
- package/dist/headers/index.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/input/index.d.ts +0 -1
- package/dist/input/index.js +0 -1
- package/dist/input/input.core.d.ts +0 -1
- package/dist/input/input.core.js +0 -1
- package/dist/input/input.decode.d.ts +0 -1
- package/dist/input/input.decode.js +0 -1
- package/dist/input/input.unsafeKey.d.ts +0 -1
- package/dist/input/input.unsafeKey.js +0 -1
- package/dist/rateLimit/index.d.ts +0 -1
- package/dist/rateLimit/index.js +0 -1
- package/dist/rateLimit/rateLimit.clientIp.d.ts +0 -1
- package/dist/rateLimit/rateLimit.clientIp.js +0 -1
- package/dist/rateLimit/rateLimit.clientKey.d.ts +0 -1
- package/dist/rateLimit/rateLimit.clientKey.js +0 -1
- package/dist/rateLimit/rateLimit.core.d.ts +2 -1
- package/dist/rateLimit/rateLimit.core.js +11 -10
- package/dist/rateLimit/rateLimit.namespace.d.ts +0 -1
- package/dist/rateLimit/rateLimit.namespace.js +0 -1
- package/dist/rateLimit/rateLimit.window.d.ts +37 -0
- package/dist/rateLimit/rateLimit.window.js +60 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/security.type.d.ts +0 -1
- package/dist/types/security.type.js +0 -1
- package/dist/url/index.d.ts +0 -1
- package/dist/url/index.js +0 -1
- package/dist/url/url.core.d.ts +0 -1
- package/dist/url/url.core.js +0 -1
- package/dist/url/url.ipv6.d.ts +0 -1
- package/dist/url/url.ipv6.js +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,7 +61,10 @@ function handle(request, response) {
|
|
|
61
61
|
A genuine sliding window: each check prunes entries older than `windowMs` and
|
|
62
62
|
decides against what remains, so a client cannot spend a full allowance either
|
|
63
63
|
side of a fixed boundary. Only allowed requests consume an allowance slot, so a
|
|
64
|
-
client being limited cannot grow its own bucket.
|
|
64
|
+
client being limited cannot grow its own bucket. Expiry is a binary search over
|
|
65
|
+
an ordered hit log with amortised compaction, so a check costs O(log `max`)
|
|
66
|
+
rather than O(`max`): a limiter with `max: 1_000_000` is as cheap per request
|
|
67
|
+
as one with `max: 300`.
|
|
65
68
|
|
|
66
69
|
```typescript
|
|
67
70
|
const limiter = createRateLimiter({
|
package/dist/body/body.core.d.ts
CHANGED
package/dist/body/body.core.js
CHANGED
package/dist/body/body.guard.js
CHANGED
package/dist/body/index.d.ts
CHANGED
|
@@ -2,4 +2,3 @@
|
|
|
2
2
|
* @zudojs/security — Body Validation Barrel
|
|
3
3
|
*/
|
|
4
4
|
export { DEFAULT_BODY_LIMITS, parseMediaType, validateBodyFraming, validateContentLength, validateBodySize, getBodyLimitForContentType, validateBodyLimitConfig, resolveBodyLimit, createBodySizeChecker, } from "./body.core.js";
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/body/index.js
CHANGED
|
@@ -2,4 +2,3 @@
|
|
|
2
2
|
* @zudojs/security — Body Validation Barrel
|
|
3
3
|
*/
|
|
4
4
|
export { DEFAULT_BODY_LIMITS, parseMediaType, validateBodyFraming, validateContentLength, validateBodySize, getBodyLimitForContentType, validateBodyLimitConfig, resolveBodyLimit, createBodySizeChecker, } from "./body.core.js";
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -71,4 +71,3 @@ export declare function validateCookieValue(value: string): string | undefined;
|
|
|
71
71
|
* @returns The cleaned cookie header.
|
|
72
72
|
*/
|
|
73
73
|
export declare function stripSensitiveCookies(cookieHeader: string, sensitiveNames?: readonly string[]): string;
|
|
74
|
-
//# sourceMappingURL=cookie.core.d.ts.map
|
|
@@ -24,4 +24,3 @@ export declare function cookieNameWords(name: string): string[];
|
|
|
24
24
|
* `__Host-session`. Word boundaries keep `theme` or `sidebar` from matching.
|
|
25
25
|
*/
|
|
26
26
|
export declare function isSensitiveCookieName(name: string, sensitiveNames?: readonly string[]): boolean;
|
|
27
|
-
//# sourceMappingURL=cookie.sensitive.d.ts.map
|
package/dist/cookie/index.d.ts
CHANGED
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { parseCookieHeader, serializeCookie, createSecureCookie, validateCookieName, validateCookieValue, stripSensitiveCookies, } from "./cookie.core.js";
|
|
5
5
|
export { DEFAULT_SENSITIVE_COOKIE_NAMES, isSensitiveCookieName, } from "./cookie.sensitive.js";
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/cookie/index.js
CHANGED
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { parseCookieHeader, serializeCookie, createSecureCookie, validateCookieName, validateCookieValue, stripSensitiveCookies, } from "./cookie.core.js";
|
|
5
5
|
export { DEFAULT_SENSITIVE_COOKIE_NAMES, isSensitiveCookieName, } from "./cookie.sensitive.js";
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
package/dist/cors/cors.core.d.ts
CHANGED
|
@@ -62,4 +62,3 @@ export declare function isMethodAllowed(method: string, config: CorsConfig): boo
|
|
|
62
62
|
* @returns An array of disallowed headers, or empty array if all allowed.
|
|
63
63
|
*/
|
|
64
64
|
export declare function getDisallowedHeaders(headers: string[], config: CorsConfig): string[];
|
|
65
|
-
//# sourceMappingURL=cors.core.d.ts.map
|
package/dist/cors/cors.core.js
CHANGED
|
@@ -186,4 +186,3 @@ export function getDisallowedHeaders(headers, config) {
|
|
|
186
186
|
const allowedHeaders = new Set((config.allowedHeaders ?? DEFAULT_CORS_CONFIG.allowedHeaders).map((h) => h.toLowerCase()));
|
|
187
187
|
return headers.filter((h) => !allowedHeaders.has(h.trim().toLowerCase()));
|
|
188
188
|
}
|
|
189
|
-
//# sourceMappingURL=cors.core.js.map
|
package/dist/cors/index.d.ts
CHANGED
package/dist/cors/index.js
CHANGED
package/dist/csrf/csrf.core.d.ts
CHANGED
package/dist/csrf/csrf.core.js
CHANGED
package/dist/csrf/index.d.ts
CHANGED
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { generateCsrfToken, validateCsrfToken, verifyDoubleSubmit, requiresCsrfProtection, extractCsrfTokenFromHeaders, extractCsrfTokenFromCookies, generateCsrfCookie, createCsrfProtection, MIN_CSRF_SECRET_LENGTH, } from "./csrf.core.js";
|
|
5
5
|
export type { CsrfTokenOptions, CsrfCookieOptions, CsrfProtection, CsrfProtectionOptions, IssuedCsrfToken, CsrfVerifiableRequest, } from "./csrf.core.js";
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/csrf/index.js
CHANGED
|
@@ -2,4 +2,3 @@
|
|
|
2
2
|
* @zudojs/security — CSRF Protection Barrel
|
|
3
3
|
*/
|
|
4
4
|
export { generateCsrfToken, validateCsrfToken, verifyDoubleSubmit, requiresCsrfProtection, extractCsrfTokenFromHeaders, extractCsrfTokenFromCookies, generateCsrfCookie, createCsrfProtection, MIN_CSRF_SECRET_LENGTH, } from "./csrf.core.js";
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
package/dist/header/index.d.ts
CHANGED
package/dist/header/index.js
CHANGED
package/dist/headers/index.d.ts
CHANGED
package/dist/headers/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -46,4 +46,3 @@ export { rateLimit } from "./rateLimit/rateLimit.namespace.js";
|
|
|
46
46
|
export { SECURITY_HEADER_NAMES } from "./headers/index.js";
|
|
47
47
|
export { generateSecurityHeaders, getMissingSecurityHeaders, generateCspNonce, validateCspDirective, } from "./headers/index.js";
|
|
48
48
|
export { containsSqlInjection, containsXss, containsPrototypePollution, sanitizeString, sanitizeObject, isSafeString, withoutStickyFlags, detectThreats, escapeHtml, stripHtml, findUnsafeKey, } from "./input/index.js";
|
|
49
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -49,4 +49,3 @@ export { SECURITY_HEADER_NAMES } from "./headers/index.js";
|
|
|
49
49
|
export { generateSecurityHeaders, getMissingSecurityHeaders, generateCspNonce, validateCspDirective, } from "./headers/index.js";
|
|
50
50
|
/* ─── Input Sanitization ─────────────────────────────────────────────────── */
|
|
51
51
|
export { containsSqlInjection, containsXss, containsPrototypePollution, sanitizeString, sanitizeObject, isSafeString, withoutStickyFlags, detectThreats, escapeHtml, stripHtml, findUnsafeKey, } from "./input/index.js";
|
|
52
|
-
//# sourceMappingURL=index.js.map
|
package/dist/input/index.d.ts
CHANGED
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { containsSqlInjection, containsXss, containsPrototypePollution, sanitizeString, sanitizeObject, isSafeString, withoutStickyFlags, detectThreats, escapeHtml, stripHtml, } from "./input.core.js";
|
|
5
5
|
export { findUnsafeKey } from "./input.unsafeKey.js";
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/input/index.js
CHANGED
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { containsSqlInjection, containsXss, containsPrototypePollution, sanitizeString, sanitizeObject, isSafeString, withoutStickyFlags, detectThreats, escapeHtml, stripHtml, } from "./input.core.js";
|
|
5
5
|
export { findUnsafeKey } from "./input.unsafeKey.js";
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
package/dist/input/input.core.js
CHANGED
|
@@ -10,4 +10,3 @@ export { extractClientIp } from "./rateLimit.clientIp.js";
|
|
|
10
10
|
export type { ClientIpOptions } from "./rateLimit.clientIp.js";
|
|
11
11
|
export { createIpKeyGenerator, ipRateLimitKey, parseClientIp, DEFAULT_IPV6_PREFIX_LENGTH, } from "./rateLimit.clientKey.js";
|
|
12
12
|
export type { IpKeyOptions } from "./rateLimit.clientKey.js";
|
|
13
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/rateLimit/index.js
CHANGED
|
@@ -7,4 +7,3 @@
|
|
|
7
7
|
export { defaultKeyGenerator, defaultHandler, retryAfterSeconds, createRateLimiter, } from "./rateLimit.core.js";
|
|
8
8
|
export { extractClientIp } from "./rateLimit.clientIp.js";
|
|
9
9
|
export { createIpKeyGenerator, ipRateLimitKey, parseClientIp, DEFAULT_IPV6_PREFIX_LENGTH, } from "./rateLimit.clientKey.js";
|
|
10
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -37,4 +37,3 @@ export interface ClientIpOptions {
|
|
|
37
37
|
* rate-limit key generator refuses `"unknown"`; pass `remoteAddress`.
|
|
38
38
|
*/
|
|
39
39
|
export declare function extractClientIp(headers: Record<string, string | string[] | undefined>, options?: ClientIpOptions): string;
|
|
40
|
-
//# sourceMappingURL=rateLimit.clientIp.d.ts.map
|
|
@@ -48,4 +48,3 @@ export declare function ipRateLimitKey(value: string, options?: IpKeyOptions): s
|
|
|
48
48
|
* @throws {RangeError} when `ipv6PrefixLength` is not an integer in 1–128.
|
|
49
49
|
*/
|
|
50
50
|
export declare function createIpKeyGenerator(options?: IpKeyOptions): (request: RateLimitRequest) => string;
|
|
51
|
-
//# sourceMappingURL=rateLimit.clientKey.d.ts.map
|
|
@@ -54,6 +54,8 @@ export interface RateLimiterOptions extends RateLimitConfig {
|
|
|
54
54
|
* The window genuinely slides: each check prunes timestamps older than
|
|
55
55
|
* `windowMs` and decides against what remains, so a client cannot spend a full
|
|
56
56
|
* allowance either side of a fixed boundary and get `2 × max` back to back.
|
|
57
|
+
* Pruning is a binary search over an ordered log (see `rateLimit.window.ts`),
|
|
58
|
+
* so the cost of a check is O(log max), not O(max).
|
|
57
59
|
*
|
|
58
60
|
* @param config - Rate limit configuration.
|
|
59
61
|
* @returns A function that checks rate limits.
|
|
@@ -68,4 +70,3 @@ export declare function createRateLimiter(config: RateLimiterOptions): {
|
|
|
68
70
|
/** Number of keys currently tracked. */
|
|
69
71
|
readonly size: number;
|
|
70
72
|
};
|
|
71
|
-
//# sourceMappingURL=rateLimit.core.d.ts.map
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Implements sliding window rate limiting to prevent abuse.
|
|
5
5
|
*/
|
|
6
6
|
import { createIpKeyGenerator, ipRateLimitKey } from "./rateLimit.clientKey.js";
|
|
7
|
+
import { createSlidingWindow, pruneWindow, recordHit, windowCount, windowOldest, } from "./rateLimit.window.js";
|
|
7
8
|
/** Default window: 1 minute. */
|
|
8
9
|
const DEFAULT_WINDOW_MS = 60_000;
|
|
9
10
|
/** Default rate limit message. */
|
|
@@ -75,6 +76,8 @@ export function retryAfterSeconds(result, now = Date.now()) {
|
|
|
75
76
|
* The window genuinely slides: each check prunes timestamps older than
|
|
76
77
|
* `windowMs` and decides against what remains, so a client cannot spend a full
|
|
77
78
|
* allowance either side of a fixed boundary and get `2 × max` back to back.
|
|
79
|
+
* Pruning is a binary search over an ordered log (see `rateLimit.window.ts`),
|
|
80
|
+
* so the cost of a check is O(log max), not O(max).
|
|
78
81
|
*
|
|
79
82
|
* @param config - Rate limit configuration.
|
|
80
83
|
* @returns A function that checks rate limits.
|
|
@@ -155,7 +158,7 @@ export function createRateLimiter(config) {
|
|
|
155
158
|
const windowStart = now - config.windowMs;
|
|
156
159
|
let entry = store.get(key);
|
|
157
160
|
if (!entry) {
|
|
158
|
-
entry = {
|
|
161
|
+
entry = { window: createSlidingWindow(), lastSeen: now };
|
|
159
162
|
store.set(key, entry);
|
|
160
163
|
evictIfNeeded();
|
|
161
164
|
}
|
|
@@ -165,19 +168,18 @@ export function createRateLimiter(config) {
|
|
|
165
168
|
store.set(key, entry);
|
|
166
169
|
}
|
|
167
170
|
// Prune everything that has slid out of the window.
|
|
168
|
-
|
|
171
|
+
pruneWindow(entry.window, windowStart);
|
|
169
172
|
entry.lastSeen = now;
|
|
170
|
-
const allowed =
|
|
173
|
+
const allowed = windowCount(entry.window) < config.max;
|
|
171
174
|
// Only an allowed request consumes an allowance slot. Recording denied
|
|
172
175
|
// requests too would let a client already over the limit keep growing its
|
|
173
176
|
// own bucket, so the cost of an attack would scale with the attack.
|
|
174
177
|
if (allowed) {
|
|
175
|
-
|
|
178
|
+
recordHit(entry.window, now);
|
|
176
179
|
}
|
|
177
|
-
|
|
178
|
-
const remaining = Math.max(0, config.max - timestamps.length);
|
|
180
|
+
const remaining = Math.max(0, config.max - windowCount(entry.window));
|
|
179
181
|
// The window frees up when its oldest surviving request ages out.
|
|
180
|
-
const oldest =
|
|
182
|
+
const oldest = windowOldest(entry.window);
|
|
181
183
|
const resetAt = new Date((oldest ?? now) + config.windowMs);
|
|
182
184
|
return {
|
|
183
185
|
allowed,
|
|
@@ -215,8 +217,8 @@ export function createRateLimiter(config) {
|
|
|
215
217
|
const entry = store.get(storeKey(key));
|
|
216
218
|
if (!entry)
|
|
217
219
|
return 0;
|
|
218
|
-
|
|
219
|
-
return entry.
|
|
220
|
+
pruneWindow(entry.window, Date.now() - config.windowMs);
|
|
221
|
+
return windowCount(entry.window);
|
|
220
222
|
}
|
|
221
223
|
/**
|
|
222
224
|
* Destroys the rate limiter and cleans up resources.
|
|
@@ -238,4 +240,3 @@ export function createRateLimiter(config) {
|
|
|
238
240
|
},
|
|
239
241
|
};
|
|
240
242
|
}
|
|
241
|
-
//# sourceMappingURL=rateLimit.core.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/security — Sliding-window bookkeeping for the rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* A key's allowed hits are kept in chronological order behind a head
|
|
5
|
+
* offset. Expiring old hits is a binary search that advances the head, and
|
|
6
|
+
* the array is compacted only once the dead prefix outgrows the live
|
|
7
|
+
* suffix, so every operation is O(log n) amortised. The previous
|
|
8
|
+
* `timestamps.filter(...)` copied the whole array on every check, which
|
|
9
|
+
* made the cost of one request proportional to `max`: a limiter with
|
|
10
|
+
* `max: 1e6` spent more than twenty percent of a core on a single busy
|
|
11
|
+
* client where `max: 300` spent three.
|
|
12
|
+
*/
|
|
13
|
+
/** Allowed hits inside a key's window, oldest first from `head`. */
|
|
14
|
+
export interface SlidingWindow {
|
|
15
|
+
/** Monotonic hit timestamps; entries before `head` have expired. */
|
|
16
|
+
timestamps: number[];
|
|
17
|
+
/** Index of the oldest live hit. */
|
|
18
|
+
head: number;
|
|
19
|
+
}
|
|
20
|
+
/** Create an empty window. */
|
|
21
|
+
export declare function createSlidingWindow(): SlidingWindow;
|
|
22
|
+
/**
|
|
23
|
+
* Drop every hit at or before `windowStart`.
|
|
24
|
+
*
|
|
25
|
+
* Timestamps are appended in arrival order, so the live region is a suffix
|
|
26
|
+
* and its start can be found by binary search. Compaction copies the live
|
|
27
|
+
* suffix only when the dead prefix is at least as long, which bounds both
|
|
28
|
+
* the copy cost (amortised O(1) per hit) and the retained memory (at most
|
|
29
|
+
* twice the live hits).
|
|
30
|
+
*/
|
|
31
|
+
export declare function pruneWindow(window: SlidingWindow, windowStart: number): void;
|
|
32
|
+
/** Number of live hits. */
|
|
33
|
+
export declare function windowCount(window: SlidingWindow): number;
|
|
34
|
+
/** Timestamp of the oldest live hit, if any. */
|
|
35
|
+
export declare function windowOldest(window: SlidingWindow): number | undefined;
|
|
36
|
+
/** Record an allowed hit at `now`. */
|
|
37
|
+
export declare function recordHit(window: SlidingWindow, now: number): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/security — Sliding-window bookkeeping for the rate limiter.
|
|
3
|
+
*
|
|
4
|
+
* A key's allowed hits are kept in chronological order behind a head
|
|
5
|
+
* offset. Expiring old hits is a binary search that advances the head, and
|
|
6
|
+
* the array is compacted only once the dead prefix outgrows the live
|
|
7
|
+
* suffix, so every operation is O(log n) amortised. The previous
|
|
8
|
+
* `timestamps.filter(...)` copied the whole array on every check, which
|
|
9
|
+
* made the cost of one request proportional to `max`: a limiter with
|
|
10
|
+
* `max: 1e6` spent more than twenty percent of a core on a single busy
|
|
11
|
+
* client where `max: 300` spent three.
|
|
12
|
+
*/
|
|
13
|
+
/** Create an empty window. */
|
|
14
|
+
export function createSlidingWindow() {
|
|
15
|
+
return { timestamps: [], head: 0 };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Drop every hit at or before `windowStart`.
|
|
19
|
+
*
|
|
20
|
+
* Timestamps are appended in arrival order, so the live region is a suffix
|
|
21
|
+
* and its start can be found by binary search. Compaction copies the live
|
|
22
|
+
* suffix only when the dead prefix is at least as long, which bounds both
|
|
23
|
+
* the copy cost (amortised O(1) per hit) and the retained memory (at most
|
|
24
|
+
* twice the live hits).
|
|
25
|
+
*/
|
|
26
|
+
export function pruneWindow(window, windowStart) {
|
|
27
|
+
const { timestamps } = window;
|
|
28
|
+
let low = window.head;
|
|
29
|
+
let high = timestamps.length;
|
|
30
|
+
while (low < high) {
|
|
31
|
+
const middle = (low + high) >>> 1;
|
|
32
|
+
if (timestamps[middle] > windowStart) {
|
|
33
|
+
high = middle;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
low = middle + 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
window.head = low;
|
|
40
|
+
if (low === timestamps.length) {
|
|
41
|
+
window.timestamps = [];
|
|
42
|
+
window.head = 0;
|
|
43
|
+
}
|
|
44
|
+
else if (low > 0 && low * 2 >= timestamps.length) {
|
|
45
|
+
window.timestamps = timestamps.slice(low);
|
|
46
|
+
window.head = 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Number of live hits. */
|
|
50
|
+
export function windowCount(window) {
|
|
51
|
+
return window.timestamps.length - window.head;
|
|
52
|
+
}
|
|
53
|
+
/** Timestamp of the oldest live hit, if any. */
|
|
54
|
+
export function windowOldest(window) {
|
|
55
|
+
return window.timestamps[window.head];
|
|
56
|
+
}
|
|
57
|
+
/** Record an allowed hit at `now`. */
|
|
58
|
+
export function recordHit(window, now) {
|
|
59
|
+
window.timestamps.push(now);
|
|
60
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type { HeaderSecurityConfig, HeaderValidationResult, BodyLimitConfig, BodyLimitPresets, UrlValidationConfig, UrlValidationResult, CookieSecurityConfig, ParsedCookie, CorsConfig, CsrfConfig, RateLimitConfig, RateLimitRequest, RateLimitResponse, RateLimitResult, SecurityHeadersConfig, InputSanitizationConfig, } from "./security.type.js";
|
|
7
7
|
export { PROTOTYPE_POLLUTION_KEYS, SQL_INJECTION_PATTERNS, XSS_PATTERNS, } from "./security.type.js";
|
|
8
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/types/index.js
CHANGED
|
@@ -223,4 +223,3 @@ export declare const SQL_INJECTION_PATTERNS: readonly [RegExp, RegExp, RegExp, R
|
|
|
223
223
|
* a payload it matched a moment earlier.
|
|
224
224
|
*/
|
|
225
225
|
export declare const XSS_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
|
|
226
|
-
//# sourceMappingURL=security.type.d.ts.map
|
package/dist/url/index.d.ts
CHANGED
|
@@ -4,4 +4,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
6
|
export { expandIpv6, embeddedIpv4, isNonPublicIpv6Range, } from "./url.ipv6.js";
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/url/index.js
CHANGED
package/dist/url/url.core.d.ts
CHANGED
package/dist/url/url.core.js
CHANGED
package/dist/url/url.ipv6.d.ts
CHANGED
|
@@ -33,4 +33,3 @@ export declare function embeddedIpv4(groups: readonly number[]): number[] | unde
|
|
|
33
33
|
* `64:ff9b:1::/48`, whose embedded address depends on local configuration.
|
|
34
34
|
*/
|
|
35
35
|
export declare function isNonPublicIpv6Range(groups: readonly number[]): boolean;
|
|
36
|
-
//# sourceMappingURL=url.ipv6.d.ts.map
|
package/dist/url/url.ipv6.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/security",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Security primitives for input validation, header security, CORS, CSRF protection, rate limiting, and security headers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"!dist/.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/errors": "1.
|
|
28
|
-
"@zudojs/constants": "1.
|
|
27
|
+
"@zudojs/errors": "1.4.0",
|
|
28
|
+
"@zudojs/constants": "1.2.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"typescript": "7.0.2",
|