@actuate-media/cms-core 0.11.2 → 0.13.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/dist/__tests__/api/cron-routes.test.d.ts +2 -0
- package/dist/__tests__/api/cron-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/cron-routes.test.js +67 -0
- package/dist/__tests__/api/cron-routes.test.js.map +1 -0
- package/dist/__tests__/api/health.test.d.ts +2 -0
- package/dist/__tests__/api/health.test.d.ts.map +1 -0
- package/dist/__tests__/api/health.test.js +140 -0
- package/dist/__tests__/api/health.test.js.map +1 -0
- package/dist/__tests__/auth/oauth.test.d.ts +2 -0
- package/dist/__tests__/auth/oauth.test.d.ts.map +1 -0
- package/dist/__tests__/auth/oauth.test.js +406 -0
- package/dist/__tests__/auth/oauth.test.js.map +1 -0
- package/dist/__tests__/auth/password.test.js +82 -3
- package/dist/__tests__/auth/password.test.js.map +1 -1
- package/dist/__tests__/auth/reset.test.d.ts +2 -0
- package/dist/__tests__/auth/reset.test.d.ts.map +1 -0
- package/dist/__tests__/auth/reset.test.js +303 -0
- package/dist/__tests__/auth/reset.test.js.map +1 -0
- package/dist/__tests__/auth/session.test.js +54 -1
- package/dist/__tests__/auth/session.test.js.map +1 -1
- package/dist/__tests__/cron/cron.test.d.ts +2 -0
- package/dist/__tests__/cron/cron.test.d.ts.map +1 -0
- package/dist/__tests__/cron/cron.test.js +262 -0
- package/dist/__tests__/cron/cron.test.js.map +1 -0
- package/dist/__tests__/diagnostics/env.test.d.ts +2 -0
- package/dist/__tests__/diagnostics/env.test.d.ts.map +1 -0
- package/dist/__tests__/diagnostics/env.test.js +119 -0
- package/dist/__tests__/diagnostics/env.test.js.map +1 -0
- package/dist/__tests__/diagnostics/logger.test.d.ts +2 -0
- package/dist/__tests__/diagnostics/logger.test.d.ts.map +1 -0
- package/dist/__tests__/diagnostics/logger.test.js +111 -0
- package/dist/__tests__/diagnostics/logger.test.js.map +1 -0
- package/dist/__tests__/security/encrypted-fields.test.d.ts +2 -0
- package/dist/__tests__/security/encrypted-fields.test.d.ts.map +1 -0
- package/dist/__tests__/security/encrypted-fields.test.js +60 -0
- package/dist/__tests__/security/encrypted-fields.test.js.map +1 -0
- package/dist/__tests__/security/rate-limit.test.js +42 -0
- package/dist/__tests__/security/rate-limit.test.js.map +1 -1
- package/dist/__tests__/security/safe-fetch.test.d.ts +2 -0
- package/dist/__tests__/security/safe-fetch.test.d.ts.map +1 -0
- package/dist/__tests__/security/safe-fetch.test.js +97 -0
- package/dist/__tests__/security/safe-fetch.test.js.map +1 -0
- package/dist/__tests__/security/ssrf.test.d.ts +2 -0
- package/dist/__tests__/security/ssrf.test.d.ts.map +1 -0
- package/dist/__tests__/security/ssrf.test.js +209 -0
- package/dist/__tests__/security/ssrf.test.js.map +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +7 -6
- package/dist/actions.js.map +1 -1
- package/dist/api/handler-factory.d.ts.map +1 -1
- package/dist/api/handler-factory.js +15 -6
- package/dist/api/handler-factory.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +165 -26
- package/dist/api/handlers.js.map +1 -1
- package/dist/auth/oauth.d.ts +8 -0
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +44 -2
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/password.d.ts +35 -2
- package/dist/auth/password.d.ts.map +1 -1
- package/dist/auth/password.js +97 -7
- package/dist/auth/password.js.map +1 -1
- package/dist/auth/reset.d.ts.map +1 -1
- package/dist/auth/reset.js +2 -1
- package/dist/auth/reset.js.map +1 -1
- package/dist/auth/session.d.ts +9 -0
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +54 -1
- package/dist/auth/session.js.map +1 -1
- package/dist/config/runtime.d.ts +99 -0
- package/dist/config/runtime.d.ts.map +1 -0
- package/dist/config/runtime.js +43 -0
- package/dist/config/runtime.js.map +1 -0
- package/dist/config/types.d.ts +21 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/index.d.ts +72 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +222 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/diagnostics/env.d.ts +44 -0
- package/dist/diagnostics/env.d.ts.map +1 -0
- package/dist/diagnostics/env.js +293 -0
- package/dist/diagnostics/env.js.map +1 -0
- package/dist/diagnostics/logger.d.ts +38 -0
- package/dist/diagnostics/logger.d.ts.map +1 -0
- package/dist/diagnostics/logger.js +89 -0
- package/dist/diagnostics/logger.js.map +1 -0
- package/dist/page-builder/blocks.d.ts.map +1 -1
- package/dist/page-builder/blocks.js +6 -1
- package/dist/page-builder/blocks.js.map +1 -1
- package/dist/security/audit.d.ts.map +1 -1
- package/dist/security/audit.js +3 -1
- package/dist/security/audit.js.map +1 -1
- package/dist/security/encrypted-fields.d.ts +9 -0
- package/dist/security/encrypted-fields.d.ts.map +1 -1
- package/dist/security/encrypted-fields.js +52 -1
- package/dist/security/encrypted-fields.js.map +1 -1
- package/dist/security/ip-canon.d.ts +71 -0
- package/dist/security/ip-canon.d.ts.map +1 -0
- package/dist/security/ip-canon.js +352 -0
- package/dist/security/ip-canon.js.map +1 -0
- package/dist/security/rate-limit.d.ts +8 -0
- package/dist/security/rate-limit.d.ts.map +1 -1
- package/dist/security/rate-limit.js +81 -3
- package/dist/security/rate-limit.js.map +1 -1
- package/dist/security/safe-fetch.d.ts +30 -8
- package/dist/security/safe-fetch.d.ts.map +1 -1
- package/dist/security/safe-fetch.js +32 -6
- package/dist/security/safe-fetch.js.map +1 -1
- package/dist/security/webhook.d.ts +20 -2
- package/dist/security/webhook.d.ts.map +1 -1
- package/dist/security/webhook.js +100 -30
- package/dist/security/webhook.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypted-fields.js","sourceRoot":"","sources":["../../src/security/encrypted-fields.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,SAAS,CAAA;AAC3B,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,UAAU,GAAG,GAAG,CAAA;
|
|
1
|
+
{"version":3,"file":"encrypted-fields.js","sourceRoot":"","sources":["../../src/security/encrypted-fields.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP canonicalization for SSRF defense.
|
|
3
|
+
*
|
|
4
|
+
* The previous regex-based approach in `webhook.ts` only matched the literal
|
|
5
|
+
* dotted-quad form. It missed every encoding that `getaddrinfo` (and therefore
|
|
6
|
+
* `fetch`) accepts:
|
|
7
|
+
*
|
|
8
|
+
* - Decimal: http://2130706433/ → 127.0.0.1
|
|
9
|
+
* - Octal: http://0177.0.0.1/ → 127.0.0.1
|
|
10
|
+
* - Hex: http://0x7f.0.0.1/ → 127.0.0.1
|
|
11
|
+
* - Short-form: http://127.1/ → 127.0.0.1
|
|
12
|
+
* - IPv4-mapped v6: http://[::ffff:127.0.0.1] → 127.0.0.1
|
|
13
|
+
* - IPv4-compat v6: http://[::127.0.0.1] → 127.0.0.1
|
|
14
|
+
*
|
|
15
|
+
* This module canonicalizes any of those forms to a 4-octet IPv4 string (or
|
|
16
|
+
* a normalized 16-byte IPv6 address), then range-checks against a complete
|
|
17
|
+
* private/internal block list including AWS/GCP/Azure metadata endpoints and
|
|
18
|
+
* the carrier-grade NAT range.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Result of {@link canonicalizeHostname}. There are exactly three valid
|
|
22
|
+
* shapes:
|
|
23
|
+
*
|
|
24
|
+
* - **Valid IPv4 literal** — `isHostname=false`, `isValidIp=true`, `ipv4` set.
|
|
25
|
+
* - **Valid IPv6 literal** — `isHostname=false`, `isValidIp=true`, `ipv6` set.
|
|
26
|
+
* - **Hostname (DNS name)** — `isHostname=true`, `isValidIp=false`, neither
|
|
27
|
+
* `ipv4` nor `ipv6` set.
|
|
28
|
+
* - **Malformed IP literal** — `isHostname=false`, `isValidIp=false`, neither
|
|
29
|
+
* IP field set. (Inputs that look like IP literals — e.g. contain `:` or
|
|
30
|
+
* only digits and dots — but fail to parse cleanly.)
|
|
31
|
+
*
|
|
32
|
+
* SSRF gates MUST treat the malformed shape as "reject" rather than as
|
|
33
|
+
* "validated IP literal". Use `isValidIp` to distinguish the two `false`
|
|
34
|
+
* `isHostname` cases.
|
|
35
|
+
*/
|
|
36
|
+
export interface CanonicalizedHost {
|
|
37
|
+
/** Original hostname as it appeared in the URL. */
|
|
38
|
+
raw: string;
|
|
39
|
+
/** Canonical IPv4 dotted-quad string when the literal was IPv4 (in any form). */
|
|
40
|
+
ipv4?: string;
|
|
41
|
+
/** Canonical IPv6 hextet string when the literal was IPv6. */
|
|
42
|
+
ipv6?: string;
|
|
43
|
+
/** True when the input was a DNS name (not an IP literal in any form). */
|
|
44
|
+
isHostname: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* True when the input parsed cleanly into a canonical IP address (`ipv4`
|
|
47
|
+
* or `ipv6` is set). False for both DNS hostnames and malformed IP-shaped
|
|
48
|
+
* strings. Callers performing a security check on the parsed IP MUST
|
|
49
|
+
* verify `isValidIp` before reading `ipv4` / `ipv6`.
|
|
50
|
+
*/
|
|
51
|
+
isValidIp: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Canonicalize a URL hostname into either a normalized IP address or a
|
|
55
|
+
* passthrough hostname. Handles every IPv4 encoding accepted by `inet_aton`
|
|
56
|
+
* (decimal, octal, hex, short-form), IPv4-mapped IPv6, and bracketed IPv6.
|
|
57
|
+
*/
|
|
58
|
+
export declare function canonicalizeHostname(rawHostname: string): CanonicalizedHost;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the matching private/internal range when the canonicalized host is
|
|
61
|
+
* one, `null` when it's a safe public IP, or a "malformed" reason when the
|
|
62
|
+
* input claims to be an IP literal but didn't parse cleanly.
|
|
63
|
+
*
|
|
64
|
+
* Defense-in-depth: even if a caller forgets to gate on `isValidIp`, this
|
|
65
|
+
* function still fails closed on the malformed shape (`isHostname=false`
|
|
66
|
+
* with no `ipv4`/`ipv6`).
|
|
67
|
+
*/
|
|
68
|
+
export declare function isPrivateAddress(host: CanonicalizedHost): {
|
|
69
|
+
reason: string;
|
|
70
|
+
} | null;
|
|
71
|
+
//# sourceMappingURL=ip-canon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-canon.d.ts","sourceRoot":"","sources":["../../src/security/ip-canon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiCH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAA;IACX,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAA;IACnB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,CAmC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4BnF"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP canonicalization for SSRF defense.
|
|
3
|
+
*
|
|
4
|
+
* The previous regex-based approach in `webhook.ts` only matched the literal
|
|
5
|
+
* dotted-quad form. It missed every encoding that `getaddrinfo` (and therefore
|
|
6
|
+
* `fetch`) accepts:
|
|
7
|
+
*
|
|
8
|
+
* - Decimal: http://2130706433/ → 127.0.0.1
|
|
9
|
+
* - Octal: http://0177.0.0.1/ → 127.0.0.1
|
|
10
|
+
* - Hex: http://0x7f.0.0.1/ → 127.0.0.1
|
|
11
|
+
* - Short-form: http://127.1/ → 127.0.0.1
|
|
12
|
+
* - IPv4-mapped v6: http://[::ffff:127.0.0.1] → 127.0.0.1
|
|
13
|
+
* - IPv4-compat v6: http://[::127.0.0.1] → 127.0.0.1
|
|
14
|
+
*
|
|
15
|
+
* This module canonicalizes any of those forms to a 4-octet IPv4 string (or
|
|
16
|
+
* a normalized 16-byte IPv6 address), then range-checks against a complete
|
|
17
|
+
* private/internal block list including AWS/GCP/Azure metadata endpoints and
|
|
18
|
+
* the carrier-grade NAT range.
|
|
19
|
+
*/
|
|
20
|
+
const PRIVATE_IPV4_RANGES = [
|
|
21
|
+
// 0.0.0.0/8 "this network"
|
|
22
|
+
{ network: 0x00_00_00_00, mask: 0xff_00_00_00, reason: '0.0.0.0/8 (this network)' },
|
|
23
|
+
// 10.0.0.0/8 RFC1918 private
|
|
24
|
+
{ network: 0x0a_00_00_00, mask: 0xff_00_00_00, reason: '10.0.0.0/8 (RFC1918 private)' },
|
|
25
|
+
// 100.64.0.0/10 RFC6598 carrier-grade NAT
|
|
26
|
+
{ network: 0x64_40_00_00, mask: 0xff_c0_00_00, reason: '100.64.0.0/10 (CGNAT)' },
|
|
27
|
+
// 127.0.0.0/8 loopback
|
|
28
|
+
{ network: 0x7f_00_00_00, mask: 0xff_00_00_00, reason: '127.0.0.0/8 (loopback)' },
|
|
29
|
+
// 169.254.0.0/16 link-local + cloud metadata (AWS 169.254.169.254 is in here)
|
|
30
|
+
{ network: 0xa9_fe_00_00, mask: 0xff_ff_00_00, reason: '169.254.0.0/16 (link-local + metadata)' },
|
|
31
|
+
// 172.16.0.0/12 RFC1918 private
|
|
32
|
+
{ network: 0xac_10_00_00, mask: 0xff_f0_00_00, reason: '172.16.0.0/12 (RFC1918 private)' },
|
|
33
|
+
// 192.0.0.0/24 RFC6890 IETF protocol assignments
|
|
34
|
+
{ network: 0xc0_00_00_00, mask: 0xff_ff_ff_00, reason: '192.0.0.0/24 (IETF reserved)' },
|
|
35
|
+
// 192.0.2.0/24 TEST-NET-1 (documentation)
|
|
36
|
+
{ network: 0xc0_00_02_00, mask: 0xff_ff_ff_00, reason: '192.0.2.0/24 (TEST-NET-1)' },
|
|
37
|
+
// 192.168.0.0/16 RFC1918 private
|
|
38
|
+
{ network: 0xc0_a8_00_00, mask: 0xff_ff_00_00, reason: '192.168.0.0/16 (RFC1918 private)' },
|
|
39
|
+
// 198.18.0.0/15 benchmark
|
|
40
|
+
{ network: 0xc6_12_00_00, mask: 0xff_fe_00_00, reason: '198.18.0.0/15 (benchmark)' },
|
|
41
|
+
// 198.51.100.0/24 TEST-NET-2
|
|
42
|
+
{ network: 0xc6_33_64_00, mask: 0xff_ff_ff_00, reason: '198.51.100.0/24 (TEST-NET-2)' },
|
|
43
|
+
// 203.0.113.0/24 TEST-NET-3
|
|
44
|
+
{ network: 0xcb_00_71_00, mask: 0xff_ff_ff_00, reason: '203.0.113.0/24 (TEST-NET-3)' },
|
|
45
|
+
// 224.0.0.0/4 multicast
|
|
46
|
+
{ network: 0xe0_00_00_00, mask: 0xf0_00_00_00, reason: '224.0.0.0/4 (multicast)' },
|
|
47
|
+
// 240.0.0.0/4 reserved (includes 255.255.255.255 broadcast)
|
|
48
|
+
{ network: 0xf0_00_00_00, mask: 0xf0_00_00_00, reason: '240.0.0.0/4 (reserved)' },
|
|
49
|
+
];
|
|
50
|
+
/**
|
|
51
|
+
* Canonicalize a URL hostname into either a normalized IP address or a
|
|
52
|
+
* passthrough hostname. Handles every IPv4 encoding accepted by `inet_aton`
|
|
53
|
+
* (decimal, octal, hex, short-form), IPv4-mapped IPv6, and bracketed IPv6.
|
|
54
|
+
*/
|
|
55
|
+
export function canonicalizeHostname(rawHostname) {
|
|
56
|
+
const raw = rawHostname;
|
|
57
|
+
// Strip surrounding brackets (URLs use `[::1]` for IPv6 hosts).
|
|
58
|
+
let host = raw;
|
|
59
|
+
if (host.startsWith('[') && host.endsWith(']')) {
|
|
60
|
+
host = host.slice(1, -1);
|
|
61
|
+
}
|
|
62
|
+
// Try IPv6 first — only IPv6 contains ':'.
|
|
63
|
+
if (host.includes(':')) {
|
|
64
|
+
const ipv6 = parseIPv6(host);
|
|
65
|
+
if (ipv6) {
|
|
66
|
+
// IPv4-mapped (::ffff:a.b.c.d) and IPv4-compatible (::a.b.c.d) both
|
|
67
|
+
// tunnel an IPv4 address through IPv6 — collapse to the underlying v4
|
|
68
|
+
// so the v4 range check catches them.
|
|
69
|
+
const tunnelled = extractIPv4FromIPv6(ipv6);
|
|
70
|
+
if (tunnelled !== null) {
|
|
71
|
+
return { raw, ipv4: ipv4ToString(tunnelled), isHostname: false, isValidIp: true };
|
|
72
|
+
}
|
|
73
|
+
return { raw, ipv6: formatIPv6(ipv6), isHostname: false, isValidIp: true };
|
|
74
|
+
}
|
|
75
|
+
// Looked like IPv6 (had a colon) but didn't parse. Mark as a non-hostname
|
|
76
|
+
// *and* not a valid IP — SSRF gates fail closed on this shape.
|
|
77
|
+
return { raw, isHostname: false, isValidIp: false };
|
|
78
|
+
}
|
|
79
|
+
// Try IPv4 in any of: decimal, octal, hex, dotted, short-form.
|
|
80
|
+
const ipv4 = parseIPv4Literal(host);
|
|
81
|
+
if (ipv4 !== null) {
|
|
82
|
+
return { raw, ipv4: ipv4ToString(ipv4), isHostname: false, isValidIp: true };
|
|
83
|
+
}
|
|
84
|
+
// Not an IP literal in any form → treat as DNS hostname.
|
|
85
|
+
return { raw, isHostname: true, isValidIp: false };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the matching private/internal range when the canonicalized host is
|
|
89
|
+
* one, `null` when it's a safe public IP, or a "malformed" reason when the
|
|
90
|
+
* input claims to be an IP literal but didn't parse cleanly.
|
|
91
|
+
*
|
|
92
|
+
* Defense-in-depth: even if a caller forgets to gate on `isValidIp`, this
|
|
93
|
+
* function still fails closed on the malformed shape (`isHostname=false`
|
|
94
|
+
* with no `ipv4`/`ipv6`).
|
|
95
|
+
*/
|
|
96
|
+
export function isPrivateAddress(host) {
|
|
97
|
+
if (host.ipv4) {
|
|
98
|
+
const num = ipv4StringToNumber(host.ipv4);
|
|
99
|
+
if (num === null)
|
|
100
|
+
return { reason: 'invalid IPv4 numeric form' };
|
|
101
|
+
for (const range of PRIVATE_IPV4_RANGES) {
|
|
102
|
+
// `>>> 0` forces the int32 result of `&` back to an unsigned 32-bit
|
|
103
|
+
// value so comparisons with `range.network` (a positive JS number)
|
|
104
|
+
// work correctly even when the high bit is set (172.16.x.x and up).
|
|
105
|
+
if ((num & range.mask) >>> 0 === range.network) {
|
|
106
|
+
return { reason: range.reason };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (host.ipv6) {
|
|
112
|
+
const v6 = parseIPv6(host.ipv6);
|
|
113
|
+
if (!v6)
|
|
114
|
+
return { reason: 'invalid IPv6 form' };
|
|
115
|
+
return checkPrivateIPv6(v6);
|
|
116
|
+
}
|
|
117
|
+
// Malformed IP literal (looked like an IP but didn't parse). Fail closed.
|
|
118
|
+
if (!host.isHostname) {
|
|
119
|
+
return { reason: 'malformed IP literal' };
|
|
120
|
+
}
|
|
121
|
+
// DNS hostname → no IP-level check possible without resolution.
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse an IPv4 literal in *any* form `inet_aton` accepts:
|
|
126
|
+
* - 4 parts: a.b.c.d (each 0-255)
|
|
127
|
+
* - 3 parts: a.b.c (c is bottom 16 bits)
|
|
128
|
+
* - 2 parts: a.b (b is bottom 24 bits)
|
|
129
|
+
* - 1 part: a (full 32-bit number)
|
|
130
|
+
*
|
|
131
|
+
* Each part may be decimal, octal (leading 0), or hex (leading 0x).
|
|
132
|
+
*/
|
|
133
|
+
function parseIPv4Literal(input) {
|
|
134
|
+
if (input.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
const parts = input.split('.');
|
|
137
|
+
if (parts.length === 0 || parts.length > 4)
|
|
138
|
+
return null;
|
|
139
|
+
const nums = [];
|
|
140
|
+
for (const p of parts) {
|
|
141
|
+
if (p.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
const n = parsePartNumber(p);
|
|
144
|
+
if (n === null)
|
|
145
|
+
return null;
|
|
146
|
+
nums.push(n);
|
|
147
|
+
}
|
|
148
|
+
// Build 32-bit value per inet_aton rules.
|
|
149
|
+
switch (nums.length) {
|
|
150
|
+
case 1: {
|
|
151
|
+
const a = nums[0];
|
|
152
|
+
if (a > 0xff_ff_ff_ff)
|
|
153
|
+
return null;
|
|
154
|
+
return a >>> 0;
|
|
155
|
+
}
|
|
156
|
+
case 2: {
|
|
157
|
+
const [a, b] = nums;
|
|
158
|
+
if (a > 0xff || b > 0xff_ff_ff)
|
|
159
|
+
return null;
|
|
160
|
+
return ((a << 24) | b) >>> 0;
|
|
161
|
+
}
|
|
162
|
+
case 3: {
|
|
163
|
+
const [a, b, c] = nums;
|
|
164
|
+
if (a > 0xff || b > 0xff || c > 0xff_ff)
|
|
165
|
+
return null;
|
|
166
|
+
return ((a << 24) | (b << 16) | c) >>> 0;
|
|
167
|
+
}
|
|
168
|
+
case 4: {
|
|
169
|
+
const [a, b, c, d] = nums;
|
|
170
|
+
if (a > 0xff || b > 0xff || c > 0xff || d > 0xff)
|
|
171
|
+
return null;
|
|
172
|
+
return ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function parsePartNumber(part) {
|
|
179
|
+
// Hex: 0x...
|
|
180
|
+
if (part.startsWith('0x') || part.startsWith('0X')) {
|
|
181
|
+
const hex = part.slice(2);
|
|
182
|
+
if (hex.length === 0 || !/^[0-9a-fA-F]+$/.test(hex))
|
|
183
|
+
return null;
|
|
184
|
+
const n = parseInt(hex, 16);
|
|
185
|
+
return Number.isFinite(n) ? n : null;
|
|
186
|
+
}
|
|
187
|
+
// Octal: starts with 0 followed by digits (but bare "0" is decimal 0).
|
|
188
|
+
if (part.length > 1 && part.startsWith('0')) {
|
|
189
|
+
if (!/^[0-7]+$/.test(part))
|
|
190
|
+
return null;
|
|
191
|
+
const n = parseInt(part, 8);
|
|
192
|
+
return Number.isFinite(n) ? n : null;
|
|
193
|
+
}
|
|
194
|
+
// Decimal.
|
|
195
|
+
if (!/^[0-9]+$/.test(part))
|
|
196
|
+
return null;
|
|
197
|
+
const n = parseInt(part, 10);
|
|
198
|
+
return Number.isFinite(n) ? n : null;
|
|
199
|
+
}
|
|
200
|
+
function ipv4StringToNumber(s) {
|
|
201
|
+
const parts = s.split('.');
|
|
202
|
+
if (parts.length !== 4)
|
|
203
|
+
return null;
|
|
204
|
+
let n = 0;
|
|
205
|
+
for (const p of parts) {
|
|
206
|
+
const x = parseInt(p, 10);
|
|
207
|
+
if (!Number.isFinite(x) || x < 0 || x > 255)
|
|
208
|
+
return null;
|
|
209
|
+
n = (n << 8) | x;
|
|
210
|
+
}
|
|
211
|
+
return n >>> 0;
|
|
212
|
+
}
|
|
213
|
+
function ipv4ToString(n) {
|
|
214
|
+
return [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff].join('.');
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parse an IPv6 address into 8 groups of 16-bit numbers. Supports `::`
|
|
218
|
+
* compression and the `::ffff:a.b.c.d` IPv4-mapped trailing form.
|
|
219
|
+
*
|
|
220
|
+
* Reconstruction order with compression:
|
|
221
|
+
* [head hextets] [zero-fill] [tail hextets] [optional v4 suffix as 2 hextets]
|
|
222
|
+
*
|
|
223
|
+
* Without compression: just `[8 hextets, optional last replaced by v4 suffix]`.
|
|
224
|
+
*/
|
|
225
|
+
function parseIPv6(input) {
|
|
226
|
+
if (input.length === 0)
|
|
227
|
+
return null;
|
|
228
|
+
const doubleColon = input.indexOf('::');
|
|
229
|
+
let head;
|
|
230
|
+
let tail;
|
|
231
|
+
if (doubleColon >= 0) {
|
|
232
|
+
if (input.indexOf('::', doubleColon + 1) !== -1)
|
|
233
|
+
return null; // multiple ::
|
|
234
|
+
head = input
|
|
235
|
+
.slice(0, doubleColon)
|
|
236
|
+
.split(':')
|
|
237
|
+
.filter((s) => s.length > 0);
|
|
238
|
+
tail = input
|
|
239
|
+
.slice(doubleColon + 2)
|
|
240
|
+
.split(':')
|
|
241
|
+
.filter((s) => s.length > 0);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
head = input.split(':');
|
|
245
|
+
tail = [];
|
|
246
|
+
// No compression: must be exactly 8 hextets (or 7 + a trailing IPv4).
|
|
247
|
+
}
|
|
248
|
+
// The trailing group may be a dotted-quad IPv4. It's always at the end of
|
|
249
|
+
// either `tail` (when compression is present) or `head` (when not).
|
|
250
|
+
let v4Suffix = null;
|
|
251
|
+
const trailingGroupArr = tail.length > 0 ? tail : head;
|
|
252
|
+
const trailingGroup = trailingGroupArr[trailingGroupArr.length - 1] ?? '';
|
|
253
|
+
if (trailingGroup.includes('.')) {
|
|
254
|
+
const v4 = parseIPv4Literal(trailingGroup);
|
|
255
|
+
if (v4 === null)
|
|
256
|
+
return null;
|
|
257
|
+
v4Suffix = [(v4 >>> 16) & 0xff_ff, v4 & 0xff_ff];
|
|
258
|
+
if (tail.length > 0)
|
|
259
|
+
tail = tail.slice(0, -1);
|
|
260
|
+
else
|
|
261
|
+
head = head.slice(0, -1);
|
|
262
|
+
}
|
|
263
|
+
const headGroups = head.map(parseHextet);
|
|
264
|
+
const tailGroups = tail.map(parseHextet);
|
|
265
|
+
if (headGroups.includes(null) || tailGroups.includes(null))
|
|
266
|
+
return null;
|
|
267
|
+
const v4Count = v4Suffix ? 2 : 0;
|
|
268
|
+
if (doubleColon >= 0) {
|
|
269
|
+
const fillCount = 8 - headGroups.length - tailGroups.length - v4Count;
|
|
270
|
+
if (fillCount < 0)
|
|
271
|
+
return null;
|
|
272
|
+
const zeros = new Array(fillCount).fill(0);
|
|
273
|
+
const result = [
|
|
274
|
+
...headGroups,
|
|
275
|
+
...zeros,
|
|
276
|
+
...tailGroups,
|
|
277
|
+
...(v4Suffix ?? []),
|
|
278
|
+
];
|
|
279
|
+
return result.length === 8 ? result : null;
|
|
280
|
+
}
|
|
281
|
+
// No compression — must add up to exactly 8 hextets including v4 suffix.
|
|
282
|
+
const result = [...headGroups, ...(v4Suffix ?? [])];
|
|
283
|
+
return result.length === 8 ? result : null;
|
|
284
|
+
}
|
|
285
|
+
function parseHextet(s) {
|
|
286
|
+
if (s.length === 0 || s.length > 4)
|
|
287
|
+
return null;
|
|
288
|
+
if (!/^[0-9a-fA-F]+$/.test(s))
|
|
289
|
+
return null;
|
|
290
|
+
return parseInt(s, 16);
|
|
291
|
+
}
|
|
292
|
+
function formatIPv6(groups) {
|
|
293
|
+
return groups.map((g) => g.toString(16)).join(':');
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* If `groups` represents an IPv4-mapped (`::ffff:a.b.c.d`) or non-trivial
|
|
297
|
+
* IPv4-compatible (`::a.b.c.d` with a.b.c.d != ::1, deprecated) IPv6 address,
|
|
298
|
+
* return the inner 32-bit IPv4 value.
|
|
299
|
+
*
|
|
300
|
+
* IMPORTANT: We deliberately do NOT extract from `::1` (loopback) or `::`
|
|
301
|
+
* (unspecified). Those are pure IPv6 addresses, not tunneled IPv4 — treating
|
|
302
|
+
* `::1` as `0.0.0.1` would silently allow loopback bypass via the IPv4
|
|
303
|
+
* range checks (which don't include `0.0.0.1`).
|
|
304
|
+
*/
|
|
305
|
+
function extractIPv4FromIPv6(groups) {
|
|
306
|
+
// First 5 groups must be all zero (the high-order 80 bits).
|
|
307
|
+
if (groups.slice(0, 5).some((g) => g !== 0))
|
|
308
|
+
return null;
|
|
309
|
+
const isMapped = groups[5] === 0xff_ff;
|
|
310
|
+
if (isMapped) {
|
|
311
|
+
return (((groups[6] ?? 0) << 16) | (groups[7] ?? 0)) >>> 0;
|
|
312
|
+
}
|
|
313
|
+
// IPv4-compatible (deprecated by RFC4291 §2.5.5.1): only when groups[5]===0
|
|
314
|
+
// AND the embedded "IPv4" is itself a real public-looking address (i.e.
|
|
315
|
+
// the whole thing isn't just `::N` for small N). We require the high
|
|
316
|
+
// hextet of the IPv4 portion (groups[6]) to be non-zero — that excludes
|
|
317
|
+
// `::1`, `::`, `::N` for N < 65536, all of which should be treated as
|
|
318
|
+
// pure IPv6 by their own range checks (loopback, unspecified, etc).
|
|
319
|
+
const isCompat = groups[5] === 0 && (groups[6] ?? 0) !== 0;
|
|
320
|
+
if (isCompat) {
|
|
321
|
+
return (((groups[6] ?? 0) << 16) | (groups[7] ?? 0)) >>> 0;
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
function checkPrivateIPv6(groups) {
|
|
326
|
+
// ::1 loopback
|
|
327
|
+
if (groups.every((g, i) => (i === 7 ? g === 1 : g === 0))) {
|
|
328
|
+
return { reason: '::1 (IPv6 loopback)' };
|
|
329
|
+
}
|
|
330
|
+
// :: unspecified
|
|
331
|
+
if (groups.every((g) => g === 0)) {
|
|
332
|
+
return { reason: ':: (unspecified)' };
|
|
333
|
+
}
|
|
334
|
+
// fc00::/7 unique local
|
|
335
|
+
if ((groups[0] & 0xfe_00) === 0xfc_00) {
|
|
336
|
+
return { reason: 'fc00::/7 (IPv6 unique local)' };
|
|
337
|
+
}
|
|
338
|
+
// fe80::/10 link-local
|
|
339
|
+
if ((groups[0] & 0xff_c0) === 0xfe_80) {
|
|
340
|
+
return { reason: 'fe80::/10 (IPv6 link-local)' };
|
|
341
|
+
}
|
|
342
|
+
// ff00::/8 multicast
|
|
343
|
+
if ((groups[0] & 0xff_00) === 0xff_00) {
|
|
344
|
+
return { reason: 'ff00::/8 (IPv6 multicast)' };
|
|
345
|
+
}
|
|
346
|
+
// 64:ff9b::/96 well-known IPv4/IPv6 translation
|
|
347
|
+
if (groups[0] === 0x00_64 && groups[1] === 0xff_9b && groups.slice(2, 6).every((g) => g === 0)) {
|
|
348
|
+
return { reason: '64:ff9b::/96 (IPv4-IPv6 translation)' };
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=ip-canon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-canon.js","sourceRoot":"","sources":["../../src/security/ip-canon.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,14 @@ export interface RateLimiter {
|
|
|
11
11
|
export interface RateLimitConfig {
|
|
12
12
|
windowMs: number;
|
|
13
13
|
maxRequests: number;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of distinct keys retained by the in-memory limiter.
|
|
16
|
+
* When exceeded, the oldest entry (insertion order) is evicted. Defaults
|
|
17
|
+
* to 10,000 — high enough to cover any legitimate single-process traffic
|
|
18
|
+
* while bounding worst-case memory under enumeration / IP-spoofing
|
|
19
|
+
* attacks. See M1 in the engineering audit.
|
|
20
|
+
*/
|
|
21
|
+
maxKeys?: number;
|
|
14
22
|
}
|
|
15
23
|
/** Create a rate limiter backed by an in-memory sliding window (for dev/single-process). */
|
|
16
24
|
export declare function createInMemoryRateLimiter(config: RateLimitConfig): RateLimiter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/security/rate-limit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/security/rate-limit.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,IAAI,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AASD,4FAA4F;AAC5F,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAmE9E;AA2BD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAuF7E;AA6BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,CAkBtE"}
|
|
@@ -1,16 +1,61 @@
|
|
|
1
|
+
import { createLogger } from '../diagnostics/logger.js';
|
|
2
|
+
const logger = createLogger('rate-limit');
|
|
3
|
+
const DEFAULT_MAX_KEYS = 10_000;
|
|
4
|
+
/**
|
|
5
|
+
* Sweep expired entries on every Nth `check()` call. Tunable at module level
|
|
6
|
+
* so tests can stub it without exposing it via the public config surface.
|
|
7
|
+
*/
|
|
8
|
+
const SWEEP_EVERY_N_CHECKS = 256;
|
|
1
9
|
/** Create a rate limiter backed by an in-memory sliding window (for dev/single-process). */
|
|
2
10
|
export function createInMemoryRateLimiter(config) {
|
|
11
|
+
// `Map` preserves insertion order so we can use it as an LRU directly:
|
|
12
|
+
// every `check()` re-inserts the touched key (delete + set) which moves it
|
|
13
|
+
// to the tail. When we exceed the cap, `windows.keys().next().value` is the
|
|
14
|
+
// least-recently-touched entry and we evict it.
|
|
3
15
|
const windows = new Map();
|
|
16
|
+
const maxKeys = config.maxKeys && config.maxKeys > 0 ? config.maxKeys : DEFAULT_MAX_KEYS;
|
|
17
|
+
let checksSinceSweep = 0;
|
|
18
|
+
function sweepExpired(now) {
|
|
19
|
+
// Bounded periodic cleanup of expired entries. We cap iterations so a
|
|
20
|
+
// pathological burst of new keys can't make this O(n) on every call.
|
|
21
|
+
let removed = 0;
|
|
22
|
+
for (const [key, entry] of windows) {
|
|
23
|
+
if (entry.resetAt <= now) {
|
|
24
|
+
windows.delete(key);
|
|
25
|
+
removed++;
|
|
26
|
+
if (removed >= 1000)
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function evictIfFull() {
|
|
32
|
+
while (windows.size >= maxKeys) {
|
|
33
|
+
const oldestKey = windows.keys().next().value;
|
|
34
|
+
if (oldestKey === undefined)
|
|
35
|
+
break;
|
|
36
|
+
windows.delete(oldestKey);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
4
39
|
return {
|
|
5
40
|
async check(key) {
|
|
6
41
|
const now = Date.now();
|
|
42
|
+
checksSinceSweep++;
|
|
43
|
+
if (checksSinceSweep >= SWEEP_EVERY_N_CHECKS) {
|
|
44
|
+
checksSinceSweep = 0;
|
|
45
|
+
sweepExpired(now);
|
|
46
|
+
}
|
|
7
47
|
const entry = windows.get(key);
|
|
8
48
|
if (!entry || now > entry.resetAt) {
|
|
9
49
|
const resetAt = now + config.windowMs;
|
|
50
|
+
windows.delete(key);
|
|
51
|
+
evictIfFull();
|
|
10
52
|
windows.set(key, { count: 1, resetAt });
|
|
11
53
|
return { allowed: true, remaining: config.maxRequests - 1, resetAt: new Date(resetAt) };
|
|
12
54
|
}
|
|
13
55
|
entry.count++;
|
|
56
|
+
// Re-insert to mark as most-recently-used.
|
|
57
|
+
windows.delete(key);
|
|
58
|
+
windows.set(key, entry);
|
|
14
59
|
const allowed = entry.count <= config.maxRequests;
|
|
15
60
|
return {
|
|
16
61
|
allowed,
|
|
@@ -24,6 +69,31 @@ export function createInMemoryRateLimiter(config) {
|
|
|
24
69
|
},
|
|
25
70
|
};
|
|
26
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Throttle the noisy "Upstash failed" audit event so a sustained outage does
|
|
74
|
+
* not generate one DB write per request. We allow at most one audit log per
|
|
75
|
+
* `AUDIT_THROTTLE_MS`. The `console.error` line still fires on every failure
|
|
76
|
+
* for live operator visibility.
|
|
77
|
+
*/
|
|
78
|
+
const AUDIT_THROTTLE_MS = 60_000;
|
|
79
|
+
let lastAuditAt = 0;
|
|
80
|
+
async function emitDegradedAudit(reason) {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
if (now - lastAuditAt < AUDIT_THROTTLE_MS)
|
|
83
|
+
return;
|
|
84
|
+
lastAuditAt = now;
|
|
85
|
+
try {
|
|
86
|
+
// Lazy import — avoids a static dep cycle (audit.ts -> db.js -> ...).
|
|
87
|
+
const { logEvent } = await import('./audit.js');
|
|
88
|
+
await logEvent({
|
|
89
|
+
event: 'rate_limit.degraded',
|
|
90
|
+
details: { reason, throttleMs: AUDIT_THROTTLE_MS },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Audit failures are themselves logged in audit.ts; don't double-log here.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
27
97
|
/** Create a rate limiter backed by Upstash Redis (for serverless/production). */
|
|
28
98
|
export function createUpstashRateLimiter(config) {
|
|
29
99
|
const url = process.env.UPSTASH_REDIS_REST_URL;
|
|
@@ -79,8 +149,14 @@ export function createUpstashRateLimiter(config) {
|
|
|
79
149
|
}
|
|
80
150
|
catch (err) {
|
|
81
151
|
// Fail open with logging — a Redis outage should NOT take the entire
|
|
82
|
-
// CMS offline.
|
|
83
|
-
console.error
|
|
152
|
+
// CMS offline. We surface the degradation through:
|
|
153
|
+
// 1. console.error for live ops/log aggregator dashboards;
|
|
154
|
+
// 2. a throttled audit-log event so the outage is preserved in the
|
|
155
|
+
// durable security record without flooding the DB during long
|
|
156
|
+
// Upstash incidents.
|
|
157
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
158
|
+
logger.error('Upstash request failed, allowing through', { reason });
|
|
159
|
+
void emitDegradedAudit(reason);
|
|
84
160
|
return {
|
|
85
161
|
allowed: true,
|
|
86
162
|
remaining: Math.max(0, config.maxRequests - 1),
|
|
@@ -93,7 +169,9 @@ export function createUpstashRateLimiter(config) {
|
|
|
93
169
|
await redisPipeline([['DEL', `ratelimit:${key}`]]);
|
|
94
170
|
}
|
|
95
171
|
catch (err) {
|
|
96
|
-
|
|
172
|
+
logger.error('Upstash reset failed', {
|
|
173
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
174
|
+
});
|
|
97
175
|
}
|
|
98
176
|
},
|
|
99
177
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/security/rate-limit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/security/rate-limit.ts"],"names":[],"mappings":"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"}
|