@zudojs/security 1.0.1 → 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.
- package/README.md +56 -5
- package/dist/body/body.core.d.ts +6 -0
- package/dist/body/body.core.js +15 -1
- package/dist/body/body.guard.d.ts +19 -0
- package/dist/body/body.guard.js +26 -0
- package/dist/cookie/cookie.core.d.ts +10 -6
- package/dist/cookie/cookie.core.js +22 -20
- package/dist/cookie/cookie.sensitive.d.ts +27 -0
- package/dist/cookie/cookie.sensitive.js +61 -0
- package/dist/cookie/index.d.ts +1 -0
- package/dist/cookie/index.js +1 -0
- package/dist/cors/cors.core.js +2 -1
- package/dist/csrf/csrf.core.d.ts +3 -1
- package/dist/csrf/csrf.core.js +19 -5
- package/dist/headers/headers.core.js +2 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/input/input.core.js +10 -1
- package/dist/input/input.decode.d.ts +25 -0
- package/dist/input/input.decode.js +72 -0
- package/dist/rateLimit/index.d.ts +9 -2
- package/dist/rateLimit/index.js +6 -1
- package/dist/rateLimit/rateLimit.clientIp.d.ts +40 -0
- package/dist/rateLimit/rateLimit.clientIp.js +63 -0
- package/dist/rateLimit/rateLimit.clientKey.d.ts +51 -0
- package/dist/rateLimit/rateLimit.clientKey.js +104 -0
- package/dist/rateLimit/rateLimit.core.d.ts +5 -30
- package/dist/rateLimit/rateLimit.core.js +12 -73
- package/dist/rateLimit/rateLimit.namespace.d.ts +2 -1
- package/dist/rateLimit/rateLimit.namespace.js +2 -1
- package/dist/types/security.type.d.ts +1 -1
- package/dist/types/security.type.js +3 -0
- package/dist/url/index.d.ts +1 -0
- package/dist/url/index.js +1 -0
- package/dist/url/url.core.js +12 -27
- package/dist/url/url.ipv6.d.ts +36 -0
- package/dist/url/url.ipv6.js +88 -0
- package/package.json +3 -3
|
@@ -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
|
/**
|
package/dist/url/index.d.ts
CHANGED
|
@@ -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
|
package/dist/url/url.core.js
CHANGED
|
@@ -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
|
-
*
|
|
305
|
-
*
|
|
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
|
|
309
|
-
if (
|
|
312
|
+
const groups = expandIpv6(hostname);
|
|
313
|
+
if (!groups)
|
|
310
314
|
return true;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/security",
|
|
3
|
-
"version": "1.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
6
|
"author": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"!dist/.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/errors": "1.0
|
|
28
|
-
"@zudojs/constants": "1.0
|
|
27
|
+
"@zudojs/errors": "1.1.0",
|
|
28
|
+
"@zudojs/constants": "1.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"typescript": "7.0.2",
|