@arcis/node 1.0.0 → 1.2.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 (54) hide show
  1. package/README.md +156 -222
  2. package/dist/core/index.d.mts +4 -4
  3. package/dist/core/index.d.ts +4 -4
  4. package/dist/core/index.js +13 -2
  5. package/dist/core/index.js.map +1 -1
  6. package/dist/core/index.mjs +13 -2
  7. package/dist/core/index.mjs.map +1 -1
  8. package/dist/index-A-m-pPeW.d.mts +340 -0
  9. package/dist/index-CgK94hY_.d.mts +532 -0
  10. package/dist/index-Co5kPRZz.d.ts +340 -0
  11. package/dist/index-D_bdJcF0.d.ts +532 -0
  12. package/dist/index.d.mts +144 -108
  13. package/dist/index.d.ts +144 -108
  14. package/dist/index.js +1541 -211
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +1515 -212
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/logging/index.d.mts +1 -1
  19. package/dist/logging/index.d.ts +1 -1
  20. package/dist/logging/index.js +12 -1
  21. package/dist/logging/index.js.map +1 -1
  22. package/dist/logging/index.mjs +12 -1
  23. package/dist/logging/index.mjs.map +1 -1
  24. package/dist/middleware/index.d.mts +2 -2
  25. package/dist/middleware/index.d.ts +2 -2
  26. package/dist/middleware/index.js +524 -4
  27. package/dist/middleware/index.js.map +1 -1
  28. package/dist/middleware/index.mjs +517 -5
  29. package/dist/middleware/index.mjs.map +1 -1
  30. package/dist/{headers-DBQedhrb.d.mts → pii-CXcHMlnX.d.mts} +156 -2
  31. package/dist/{headers-BJq2OA0i.d.ts → pii-DhNpl7M3.d.ts} +156 -2
  32. package/dist/sanitizers/index.d.mts +2 -2
  33. package/dist/sanitizers/index.d.ts +2 -2
  34. package/dist/sanitizers/index.js +331 -3
  35. package/dist/sanitizers/index.js.map +1 -1
  36. package/dist/sanitizers/index.mjs +321 -4
  37. package/dist/sanitizers/index.mjs.map +1 -1
  38. package/dist/stores/index.d.mts +1 -1
  39. package/dist/stores/index.d.ts +1 -1
  40. package/dist/stores/index.js.map +1 -1
  41. package/dist/stores/index.mjs.map +1 -1
  42. package/dist/{types-BOdL3ZWo.d.mts → types-CsOFHoD9.d.mts} +6 -1
  43. package/dist/{types-BOdL3ZWo.d.ts → types-CsOFHoD9.d.ts} +6 -1
  44. package/dist/validation/index.d.mts +2 -2
  45. package/dist/validation/index.d.ts +2 -2
  46. package/dist/validation/index.js +504 -2
  47. package/dist/validation/index.js.map +1 -1
  48. package/dist/validation/index.mjs +498 -3
  49. package/dist/validation/index.mjs.map +1 -1
  50. package/package.json +114 -109
  51. package/dist/index-BgHPM7LC.d.ts +0 -129
  52. package/dist/index-BpT7flAQ.d.ts +0 -255
  53. package/dist/index-JaFOUKyK.d.mts +0 -255
  54. package/dist/index-nAgXexwD.d.mts +0 -129
package/dist/index.d.mts CHANGED
@@ -1,139 +1,175 @@
1
- export { C as CorsOptions, S as SecureCookieOptions, a as arcis, b as arcisFunction, c as createCors, d as createErrorHandler, e as createHeaders, f as createRateLimiter, g as createSecureCookies, b as default, h as enforceSecureCookie, i as errorHandler, r as rateLimit, s as safeCors, j as secureCookieDefaults, k as securityHeaders } from './index-JaFOUKyK.mjs';
2
- export { c as createSanitizer, d as detectCommandInjection, a as detectHeaderInjection, b as detectNoSqlInjection, e as detectPathTraversal, f as detectPrototypePollution, g as detectSql, h as detectXss, k as isDangerousNoSqlKey, l as isDangerousProtoKey, s as sanitizeCommand, m as sanitizeHeaderValue, n as sanitizeHeaders, o as sanitizeObject, p as sanitizePath, q as sanitizeSql, r as sanitizeString, t as sanitizeXss } from './headers-DBQedhrb.mjs';
3
- export { F as FileInput, V as ValidateFileOptions, a as ValidateFileResult, c as createValidator, i as isDangerousExtension, s as sanitizeFilename, v as validate, b as validateFile } from './index-nAgXexwD.mjs';
1
+ export { B as BotCategory, a as BotDetectionResult, b as BotProtectionOptions, C as CorsOptions, c as CsrfOptions, S as SecureCookieOptions, d as SlidingWindowMiddleware, e as SlidingWindowOptions, T as TokenBucketMiddleware, f as TokenBucketOptions, g as arcis, h as arcisFunction, i as botProtection, j as createCors, k as createCsrf, l as createErrorHandler, m as createHeaders, n as createRateLimiter, o as createSecureCookies, p as createSlidingWindowLimiter, q as createTokenBucketLimiter, r as csrfProtection, h as default, s as detectBot, t as enforceSecureCookie, u as errorHandler, v as generateCsrfToken, w as rateLimit, x as safeCors, y as secureCookieDefaults, z as securityHeaders, A as validateCsrfToken } from './index-CgK94hY_.mjs';
2
+ export { P as PiiMatch, F as PiiRedactOptions, G as PiiScanOptions, H as PiiType, c as createSanitizer, d as detectCommandInjection, a as detectHeaderInjection, b as detectJsonpInjection, e as detectNoSqlInjection, f as detectPathTraversal, g as detectPii, h as detectPrototypePollution, i as detectSql, j as detectSsti, k as detectXss, l as detectXxe, o as isDangerousNoSqlKey, p as isDangerousProtoKey, r as redactObjectPii, q as redactPii, s as sanitizeCommand, t as sanitizeHeaderValue, u as sanitizeHeaders, v as sanitizeJsonpCallback, w as sanitizeObject, x as sanitizePath, y as sanitizeSql, z as sanitizeSsti, A as sanitizeString, B as sanitizeXss, C as sanitizeXxe, D as scanObjectPii, E as scanPii } from './pii-CXcHMlnX.mjs';
3
+ export { E as EmailValidationOptions, a as EmailValidationResult, F as FileInput, V as ValidateFileOptions, b as ValidateFileResult, c as ValidateRedirectOptions, d as ValidateRedirectResult, e as ValidateUrlOptions, f as ValidateUrlResult, g as createValidator, i as isDangerousExtension, h as isRedirectSafe, j as isUrlSafe, k as isValidEmailSyntax, s as sanitizeFilename, v as validate, l as validateEmail, m as validateFile, n as validateRedirect, o as validateUrl, p as verifyEmailMx } from './index-A-m-pPeW.mjs';
4
+ import { IncomingMessage } from 'http';
4
5
  export { createRedactor, createSafeLogger, safeLog } from './logging/index.mjs';
5
6
  export { MemoryStore, RedisClientLike, RedisStore, RedisStoreOptions, createRedisStore } from './stores/index.mjs';
6
- export { A as ArcisFunction, a as ArcisMiddleware, b as ArcisOptions, E as ErrorHandlerOptions, F as FieldValidator, H as HeaderOptions, c as HstsOptions, d as HttpError, L as LogOptions, R as RateLimitEntry, e as RateLimitOptions, f as RateLimitResult, g as RateLimitStore, h as RateLimiterMiddleware, S as SafeLogger, i as SanitizeOptions, j as SanitizeResult, T as ThreatInfo, k as ThreatType, V as ValidationConfig, l as ValidationError, m as ValidationResult, n as ValidationSchema } from './types-BOdL3ZWo.mjs';
7
+ export { A as ArcisFunction, a as ArcisMiddleware, b as ArcisOptions, E as ErrorHandlerOptions, F as FieldValidator, H as HeaderOptions, c as HstsOptions, d as HttpError, L as LogOptions, R as RateLimitEntry, e as RateLimitOptions, f as RateLimitResult, g as RateLimitStore, h as RateLimiterMiddleware, S as SafeLogger, i as SanitizeOptions, j as SanitizeResult, T as ThreatInfo, k as ThreatType, V as ValidationConfig, l as ValidationError, m as ValidationResult, n as ValidationSchema } from './types-CsOFHoD9.mjs';
7
8
  export { ArcisError, ArcisValidationError, BLOCKED, ERRORS, HEADERS, INPUT, InputTooLargeError, RATE_LIMIT, REDACTION, RateLimitError, SanitizationError, SecurityThreatError, VALIDATION } from './core/index.mjs';
8
9
  import 'express';
9
10
 
10
11
  /**
11
- * @module @arcis/node/validation/url
12
- * SSRF (Server-Side Request Forgery) prevention
12
+ * @module @arcis/node/utils/duration
13
+ * Parse human-readable duration strings into milliseconds.
13
14
  *
14
- * Validates URLs to ensure they don't target private/internal networks,
15
- * localhost, cloud metadata endpoints, or use dangerous protocols.
15
+ * Supports: ms, s, m, h, d
16
16
  *
17
17
  * @example
18
- * import { validateUrl } from '@arcis/node';
18
+ * parseDuration('5m') // 300000
19
+ * parseDuration('2h') // 7200000
20
+ * parseDuration(60000) // 60000 (passthrough)
21
+ * parseDuration('500ms') // 500
22
+ */
23
+ /**
24
+ * Parse a duration string or number into milliseconds.
19
25
  *
20
- * // Block SSRF attempts
21
- * validateUrl('http://169.254.169.254/latest/meta-data/') // { safe: false, reason: 'link-local address' }
22
- * validateUrl('http://10.0.0.1/admin') // { safe: false, reason: 'private address (10.0.0.0/8)' }
23
- * validateUrl('http://localhost/secret') // { safe: false, reason: 'loopback address' }
24
- * validateUrl('file:///etc/passwd') // { safe: false, reason: 'disallowed protocol: file:' }
26
+ * @param value - Duration string (e.g. "5m", "2h", "30s") or number (ms)
27
+ * @returns Duration in milliseconds
28
+ * @throws {Error} If the value is not a valid duration
25
29
  *
26
- * // Allow safe URLs
27
- * validateUrl('https://api.example.com/data') // { safe: true }
30
+ * @example
31
+ * parseDuration('15m') // 900000
32
+ * parseDuration('1d') // 86400000
33
+ * parseDuration('500ms') // 500
34
+ * parseDuration(60000) // 60000
28
35
  */
29
- /** Options for URL validation */
30
- interface ValidateUrlOptions {
31
- /** Allowed protocols. Default: ['http:', 'https:'] */
32
- allowedProtocols?: string[];
33
- /** Additional hostnames to block (e.g., internal service names) */
34
- blockedHosts?: string[];
35
- /** Additional hostnames to always allow (bypass IP checks) */
36
- allowedHosts?: string[];
37
- /** Allow localhost/loopback. Default: false */
38
- allowLocalhost?: boolean;
39
- /** Allow private/internal IPs. Default: false */
40
- allowPrivate?: boolean;
41
- }
42
- /** Result of URL validation */
43
- interface ValidateUrlResult {
44
- /** Whether the URL is safe to fetch */
45
- safe: boolean;
46
- /** Reason the URL was blocked (only set when safe=false) */
47
- reason?: string;
48
- }
36
+ declare function parseDuration(value: string | number): number;
49
37
  /**
50
- * Validate a URL for SSRF safety.
51
- *
52
- * Checks:
53
- * 1. Valid URL format
54
- * 2. Allowed protocol (default: http, https only)
55
- * 3. Not localhost/loopback (127.x.x.x, ::1, localhost)
56
- * 4. Not private IP (10.x, 172.16-31.x, 192.168.x)
57
- * 5. Not link-local (169.254.x.x — includes AWS/GCP/Azure metadata)
58
- * 6. Not blocked hostname
59
- * 7. No credentials in URL (user:pass@host)
60
- *
61
- * @param url - The URL string to validate
62
- * @param options - Validation options
63
- * @returns Validation result with safe flag and optional reason
38
+ * Format milliseconds into a human-readable duration string.
39
+ *
40
+ * @param ms - Duration in milliseconds
41
+ * @returns Human-readable string (e.g. "5m", "2h 30m")
64
42
  */
65
- declare function validateUrl(url: string, options?: ValidateUrlOptions): ValidateUrlResult;
43
+ declare function formatDuration(ms: number): string;
44
+
66
45
  /**
67
- * Convenience wrapper that returns true/false.
46
+ * @module @arcis/node/utils/ip
47
+ * Platform-aware client IP detection.
68
48
  *
69
- * @param url - The URL to check
70
- * @param options - Validation options
71
- * @returns true if the URL is safe to fetch
49
+ * Prevents IP spoofing by reading platform-specific headers
50
+ * instead of blindly trusting X-Forwarded-For.
51
+ *
52
+ * @example
53
+ * // Auto-detect platform from environment
54
+ * const ip = detectClientIp(req);
55
+ *
56
+ * // Explicit platform
57
+ * const ip = detectClientIp(req, { platform: 'cloudflare' });
72
58
  */
73
- declare function isUrlSafe(url: string, options?: ValidateUrlOptions): boolean;
74
59
 
60
+ type Platform = 'auto' | 'cloudflare' | 'vercel' | 'flyio' | 'render' | 'firebase' | 'aws-alb' | 'generic';
61
+ interface DetectIpOptions {
62
+ /** Platform to use for header selection. Default: 'auto' */
63
+ platform?: Platform;
64
+ /** Number of trusted proxies (for X-Forwarded-For parsing). Default: 1 */
65
+ trustedProxyCount?: number;
66
+ }
67
+ interface RequestLike$1 {
68
+ headers: Record<string, string | string[] | undefined>;
69
+ socket?: {
70
+ remoteAddress?: string;
71
+ };
72
+ connection?: {
73
+ remoteAddress?: string;
74
+ };
75
+ ip?: string;
76
+ }
75
77
  /**
76
- * @module @arcis/node/validation/redirect
77
- * Open Redirect prevention
78
+ * Detect the real client IP address from a request.
79
+ *
80
+ * Uses platform-specific headers when available to prevent IP spoofing.
81
+ * Falls back to X-Forwarded-For (parsed from the right) and then
82
+ * the socket remote address.
78
83
  *
79
- * Prevents attackers from using your app to redirect users to malicious sites
80
- * via manipulated query parameters like ?returnUrl=http://evil.com
84
+ * @param req - HTTP request object (Express, raw http, etc.)
85
+ * @param options - Detection options
86
+ * @returns Client IP address, or 'unknown' if unresolvable
81
87
  *
82
88
  * @example
83
- * import { validateRedirect, isRedirectSafe } from '@arcis/node';
89
+ * // Auto-detect platform
90
+ * app.use((req, res, next) => {
91
+ * const clientIp = detectClientIp(req);
92
+ * console.log('Client IP:', clientIp);
93
+ * next();
94
+ * });
84
95
  *
85
- * // Block open redirects
86
- * validateRedirect('http://evil.com') // { safe: false, reason: 'absolute URL not in allowed hosts' }
87
- * validateRedirect('//evil.com') // { safe: false, reason: 'protocol-relative URL not in allowed hosts' }
88
- * validateRedirect('javascript:alert(1)') // { safe: false, reason: 'dangerous protocol: javascript:' }
96
+ * @example
97
+ * // Behind Cloudflare
98
+ * const ip = detectClientIp(req, { platform: 'cloudflare' });
89
99
  *
90
- * // Allow safe redirects
91
- * validateRedirect('/dashboard') // { safe: true }
92
- * validateRedirect('/users?page=2') // { safe: true }
93
- * validateRedirect('https://myapp.com/home', { allowedHosts: ['myapp.com'] }) // { safe: true }
100
+ * @example
101
+ * // Behind 2 proxies (e.g. CDN + load balancer)
102
+ * const ip = detectClientIp(req, { trustedProxyCount: 2 });
94
103
  */
95
- /** Options for redirect validation */
96
- interface ValidateRedirectOptions {
97
- /** Hostnames that are allowed for absolute URL redirects */
98
- allowedHosts?: string[];
99
- /** Allow protocol-relative URLs (//example.com). Default: false */
100
- allowProtocolRelative?: boolean;
101
- /** Allowed protocols for absolute URLs. Default: ['http:', 'https:'] */
102
- allowedProtocols?: string[];
103
- }
104
- /** Result of redirect validation */
105
- interface ValidateRedirectResult {
106
- /** Whether the redirect URL is safe */
107
- safe: boolean;
108
- /** Reason the redirect was blocked (only set when safe=false) */
109
- reason?: string;
110
- }
104
+ declare function detectClientIp(req: RequestLike$1 | IncomingMessage, options?: DetectIpOptions): string;
111
105
  /**
112
- * Validate a redirect URL to prevent open redirect attacks.
113
- *
114
- * Safe redirects:
115
- * - Relative paths: /dashboard, /users?page=2, ../settings
116
- * - Absolute URLs to allowed hosts (when configured)
117
- *
118
- * Blocked redirects:
119
- * - Absolute URLs to unknown hosts
120
- * - Protocol-relative URLs (//evil.com)
121
- * - javascript:, data:, vbscript:, blob: protocols
122
- * - Backslash-prefixed paths (\\evil.com — browser treats as //)
123
- * - URLs with control characters that could disguise the target
124
- *
125
- * @param url - The redirect target URL to validate
126
- * @param options - Validation options
127
- * @returns Validation result with safe flag and optional reason
106
+ * Check if an IP address is a private/internal address.
107
+ *
108
+ * Detects: loopback, private ranges (RFC 1918), link-local, IPv6 equivalents.
128
109
  */
129
- declare function validateRedirect(url: string, options?: ValidateRedirectOptions): ValidateRedirectResult;
110
+ declare function isPrivateIp(ip: string): boolean;
111
+
130
112
  /**
131
- * Convenience wrapper that returns true/false.
113
+ * @module @arcis/node/utils/fingerprint
114
+ * Deterministic request fingerprinting via SHA-256.
115
+ *
116
+ * Generates a stable hash from request characteristics for
117
+ * rate limiting keys, abuse detection, and analytics.
132
118
  *
133
- * @param url - The redirect URL to check
134
- * @param options - Validation options
135
- * @returns true if the redirect is safe
119
+ * @example
120
+ * const fp = await fingerprint(req);
121
+ * // "a3f2b8c1d4e5..."
122
+ */
123
+
124
+ interface FingerprintOptions {
125
+ /** Include IP address in fingerprint. Default: true */
126
+ ip?: boolean;
127
+ /** Include User-Agent header. Default: true */
128
+ userAgent?: boolean;
129
+ /** Include Accept header. Default: true */
130
+ accept?: boolean;
131
+ /** Include Accept-Language header. Default: true */
132
+ acceptLanguage?: boolean;
133
+ /** Include Accept-Encoding header. Default: true */
134
+ acceptEncoding?: boolean;
135
+ /** Additional custom components to include */
136
+ custom?: string[];
137
+ /** IP detection options */
138
+ ipOptions?: DetectIpOptions;
139
+ }
140
+ interface RequestLike {
141
+ headers: Record<string, string | string[] | undefined>;
142
+ socket?: {
143
+ remoteAddress?: string;
144
+ };
145
+ connection?: {
146
+ remoteAddress?: string;
147
+ };
148
+ ip?: string;
149
+ }
150
+ /**
151
+ * Generate a deterministic fingerprint for a request.
152
+ *
153
+ * Creates a SHA-256 hash from configurable request components.
154
+ * The fingerprint is stable across requests from the same client
155
+ * (same IP, browser, language settings).
156
+ *
157
+ * @param req - HTTP request object
158
+ * @param options - Fingerprint configuration
159
+ * @returns Hex-encoded SHA-256 hash (64 characters)
160
+ *
161
+ * @example
162
+ * // Default fingerprint (IP + UA + Accept headers)
163
+ * const fp = fingerprint(req);
164
+ *
165
+ * @example
166
+ * // IP-only fingerprint (for simple rate limiting)
167
+ * const fp = fingerprint(req, { userAgent: false, accept: false, acceptLanguage: false, acceptEncoding: false });
168
+ *
169
+ * @example
170
+ * // With custom components
171
+ * const fp = fingerprint(req, { custom: [req.body?.userId] });
136
172
  */
137
- declare function isRedirectSafe(url: string, options?: ValidateRedirectOptions): boolean;
173
+ declare function fingerprint(req: RequestLike, options?: FingerprintOptions): string;
138
174
 
139
- export { type ValidateRedirectOptions, type ValidateRedirectResult, type ValidateUrlOptions, type ValidateUrlResult, isRedirectSafe, isUrlSafe, validateRedirect, validateUrl };
175
+ export { type DetectIpOptions, type FingerprintOptions, type Platform, detectClientIp, fingerprint, formatDuration, isPrivateIp, parseDuration };
package/dist/index.d.ts CHANGED
@@ -1,139 +1,175 @@
1
- export { C as CorsOptions, S as SecureCookieOptions, a as arcis, b as arcisFunction, c as createCors, d as createErrorHandler, e as createHeaders, f as createRateLimiter, g as createSecureCookies, b as default, h as enforceSecureCookie, i as errorHandler, r as rateLimit, s as safeCors, j as secureCookieDefaults, k as securityHeaders } from './index-BpT7flAQ.js';
2
- export { c as createSanitizer, d as detectCommandInjection, a as detectHeaderInjection, b as detectNoSqlInjection, e as detectPathTraversal, f as detectPrototypePollution, g as detectSql, h as detectXss, k as isDangerousNoSqlKey, l as isDangerousProtoKey, s as sanitizeCommand, m as sanitizeHeaderValue, n as sanitizeHeaders, o as sanitizeObject, p as sanitizePath, q as sanitizeSql, r as sanitizeString, t as sanitizeXss } from './headers-BJq2OA0i.js';
3
- export { F as FileInput, V as ValidateFileOptions, a as ValidateFileResult, c as createValidator, i as isDangerousExtension, s as sanitizeFilename, v as validate, b as validateFile } from './index-BgHPM7LC.js';
1
+ export { B as BotCategory, a as BotDetectionResult, b as BotProtectionOptions, C as CorsOptions, c as CsrfOptions, S as SecureCookieOptions, d as SlidingWindowMiddleware, e as SlidingWindowOptions, T as TokenBucketMiddleware, f as TokenBucketOptions, g as arcis, h as arcisFunction, i as botProtection, j as createCors, k as createCsrf, l as createErrorHandler, m as createHeaders, n as createRateLimiter, o as createSecureCookies, p as createSlidingWindowLimiter, q as createTokenBucketLimiter, r as csrfProtection, h as default, s as detectBot, t as enforceSecureCookie, u as errorHandler, v as generateCsrfToken, w as rateLimit, x as safeCors, y as secureCookieDefaults, z as securityHeaders, A as validateCsrfToken } from './index-D_bdJcF0.js';
2
+ export { P as PiiMatch, F as PiiRedactOptions, G as PiiScanOptions, H as PiiType, c as createSanitizer, d as detectCommandInjection, a as detectHeaderInjection, b as detectJsonpInjection, e as detectNoSqlInjection, f as detectPathTraversal, g as detectPii, h as detectPrototypePollution, i as detectSql, j as detectSsti, k as detectXss, l as detectXxe, o as isDangerousNoSqlKey, p as isDangerousProtoKey, r as redactObjectPii, q as redactPii, s as sanitizeCommand, t as sanitizeHeaderValue, u as sanitizeHeaders, v as sanitizeJsonpCallback, w as sanitizeObject, x as sanitizePath, y as sanitizeSql, z as sanitizeSsti, A as sanitizeString, B as sanitizeXss, C as sanitizeXxe, D as scanObjectPii, E as scanPii } from './pii-DhNpl7M3.js';
3
+ export { E as EmailValidationOptions, a as EmailValidationResult, F as FileInput, V as ValidateFileOptions, b as ValidateFileResult, c as ValidateRedirectOptions, d as ValidateRedirectResult, e as ValidateUrlOptions, f as ValidateUrlResult, g as createValidator, i as isDangerousExtension, h as isRedirectSafe, j as isUrlSafe, k as isValidEmailSyntax, s as sanitizeFilename, v as validate, l as validateEmail, m as validateFile, n as validateRedirect, o as validateUrl, p as verifyEmailMx } from './index-Co5kPRZz.js';
4
+ import { IncomingMessage } from 'http';
4
5
  export { createRedactor, createSafeLogger, safeLog } from './logging/index.js';
5
6
  export { MemoryStore, RedisClientLike, RedisStore, RedisStoreOptions, createRedisStore } from './stores/index.js';
6
- export { A as ArcisFunction, a as ArcisMiddleware, b as ArcisOptions, E as ErrorHandlerOptions, F as FieldValidator, H as HeaderOptions, c as HstsOptions, d as HttpError, L as LogOptions, R as RateLimitEntry, e as RateLimitOptions, f as RateLimitResult, g as RateLimitStore, h as RateLimiterMiddleware, S as SafeLogger, i as SanitizeOptions, j as SanitizeResult, T as ThreatInfo, k as ThreatType, V as ValidationConfig, l as ValidationError, m as ValidationResult, n as ValidationSchema } from './types-BOdL3ZWo.js';
7
+ export { A as ArcisFunction, a as ArcisMiddleware, b as ArcisOptions, E as ErrorHandlerOptions, F as FieldValidator, H as HeaderOptions, c as HstsOptions, d as HttpError, L as LogOptions, R as RateLimitEntry, e as RateLimitOptions, f as RateLimitResult, g as RateLimitStore, h as RateLimiterMiddleware, S as SafeLogger, i as SanitizeOptions, j as SanitizeResult, T as ThreatInfo, k as ThreatType, V as ValidationConfig, l as ValidationError, m as ValidationResult, n as ValidationSchema } from './types-CsOFHoD9.js';
7
8
  export { ArcisError, ArcisValidationError, BLOCKED, ERRORS, HEADERS, INPUT, InputTooLargeError, RATE_LIMIT, REDACTION, RateLimitError, SanitizationError, SecurityThreatError, VALIDATION } from './core/index.js';
8
9
  import 'express';
9
10
 
10
11
  /**
11
- * @module @arcis/node/validation/url
12
- * SSRF (Server-Side Request Forgery) prevention
12
+ * @module @arcis/node/utils/duration
13
+ * Parse human-readable duration strings into milliseconds.
13
14
  *
14
- * Validates URLs to ensure they don't target private/internal networks,
15
- * localhost, cloud metadata endpoints, or use dangerous protocols.
15
+ * Supports: ms, s, m, h, d
16
16
  *
17
17
  * @example
18
- * import { validateUrl } from '@arcis/node';
18
+ * parseDuration('5m') // 300000
19
+ * parseDuration('2h') // 7200000
20
+ * parseDuration(60000) // 60000 (passthrough)
21
+ * parseDuration('500ms') // 500
22
+ */
23
+ /**
24
+ * Parse a duration string or number into milliseconds.
19
25
  *
20
- * // Block SSRF attempts
21
- * validateUrl('http://169.254.169.254/latest/meta-data/') // { safe: false, reason: 'link-local address' }
22
- * validateUrl('http://10.0.0.1/admin') // { safe: false, reason: 'private address (10.0.0.0/8)' }
23
- * validateUrl('http://localhost/secret') // { safe: false, reason: 'loopback address' }
24
- * validateUrl('file:///etc/passwd') // { safe: false, reason: 'disallowed protocol: file:' }
26
+ * @param value - Duration string (e.g. "5m", "2h", "30s") or number (ms)
27
+ * @returns Duration in milliseconds
28
+ * @throws {Error} If the value is not a valid duration
25
29
  *
26
- * // Allow safe URLs
27
- * validateUrl('https://api.example.com/data') // { safe: true }
30
+ * @example
31
+ * parseDuration('15m') // 900000
32
+ * parseDuration('1d') // 86400000
33
+ * parseDuration('500ms') // 500
34
+ * parseDuration(60000) // 60000
28
35
  */
29
- /** Options for URL validation */
30
- interface ValidateUrlOptions {
31
- /** Allowed protocols. Default: ['http:', 'https:'] */
32
- allowedProtocols?: string[];
33
- /** Additional hostnames to block (e.g., internal service names) */
34
- blockedHosts?: string[];
35
- /** Additional hostnames to always allow (bypass IP checks) */
36
- allowedHosts?: string[];
37
- /** Allow localhost/loopback. Default: false */
38
- allowLocalhost?: boolean;
39
- /** Allow private/internal IPs. Default: false */
40
- allowPrivate?: boolean;
41
- }
42
- /** Result of URL validation */
43
- interface ValidateUrlResult {
44
- /** Whether the URL is safe to fetch */
45
- safe: boolean;
46
- /** Reason the URL was blocked (only set when safe=false) */
47
- reason?: string;
48
- }
36
+ declare function parseDuration(value: string | number): number;
49
37
  /**
50
- * Validate a URL for SSRF safety.
51
- *
52
- * Checks:
53
- * 1. Valid URL format
54
- * 2. Allowed protocol (default: http, https only)
55
- * 3. Not localhost/loopback (127.x.x.x, ::1, localhost)
56
- * 4. Not private IP (10.x, 172.16-31.x, 192.168.x)
57
- * 5. Not link-local (169.254.x.x — includes AWS/GCP/Azure metadata)
58
- * 6. Not blocked hostname
59
- * 7. No credentials in URL (user:pass@host)
60
- *
61
- * @param url - The URL string to validate
62
- * @param options - Validation options
63
- * @returns Validation result with safe flag and optional reason
38
+ * Format milliseconds into a human-readable duration string.
39
+ *
40
+ * @param ms - Duration in milliseconds
41
+ * @returns Human-readable string (e.g. "5m", "2h 30m")
64
42
  */
65
- declare function validateUrl(url: string, options?: ValidateUrlOptions): ValidateUrlResult;
43
+ declare function formatDuration(ms: number): string;
44
+
66
45
  /**
67
- * Convenience wrapper that returns true/false.
46
+ * @module @arcis/node/utils/ip
47
+ * Platform-aware client IP detection.
68
48
  *
69
- * @param url - The URL to check
70
- * @param options - Validation options
71
- * @returns true if the URL is safe to fetch
49
+ * Prevents IP spoofing by reading platform-specific headers
50
+ * instead of blindly trusting X-Forwarded-For.
51
+ *
52
+ * @example
53
+ * // Auto-detect platform from environment
54
+ * const ip = detectClientIp(req);
55
+ *
56
+ * // Explicit platform
57
+ * const ip = detectClientIp(req, { platform: 'cloudflare' });
72
58
  */
73
- declare function isUrlSafe(url: string, options?: ValidateUrlOptions): boolean;
74
59
 
60
+ type Platform = 'auto' | 'cloudflare' | 'vercel' | 'flyio' | 'render' | 'firebase' | 'aws-alb' | 'generic';
61
+ interface DetectIpOptions {
62
+ /** Platform to use for header selection. Default: 'auto' */
63
+ platform?: Platform;
64
+ /** Number of trusted proxies (for X-Forwarded-For parsing). Default: 1 */
65
+ trustedProxyCount?: number;
66
+ }
67
+ interface RequestLike$1 {
68
+ headers: Record<string, string | string[] | undefined>;
69
+ socket?: {
70
+ remoteAddress?: string;
71
+ };
72
+ connection?: {
73
+ remoteAddress?: string;
74
+ };
75
+ ip?: string;
76
+ }
75
77
  /**
76
- * @module @arcis/node/validation/redirect
77
- * Open Redirect prevention
78
+ * Detect the real client IP address from a request.
79
+ *
80
+ * Uses platform-specific headers when available to prevent IP spoofing.
81
+ * Falls back to X-Forwarded-For (parsed from the right) and then
82
+ * the socket remote address.
78
83
  *
79
- * Prevents attackers from using your app to redirect users to malicious sites
80
- * via manipulated query parameters like ?returnUrl=http://evil.com
84
+ * @param req - HTTP request object (Express, raw http, etc.)
85
+ * @param options - Detection options
86
+ * @returns Client IP address, or 'unknown' if unresolvable
81
87
  *
82
88
  * @example
83
- * import { validateRedirect, isRedirectSafe } from '@arcis/node';
89
+ * // Auto-detect platform
90
+ * app.use((req, res, next) => {
91
+ * const clientIp = detectClientIp(req);
92
+ * console.log('Client IP:', clientIp);
93
+ * next();
94
+ * });
84
95
  *
85
- * // Block open redirects
86
- * validateRedirect('http://evil.com') // { safe: false, reason: 'absolute URL not in allowed hosts' }
87
- * validateRedirect('//evil.com') // { safe: false, reason: 'protocol-relative URL not in allowed hosts' }
88
- * validateRedirect('javascript:alert(1)') // { safe: false, reason: 'dangerous protocol: javascript:' }
96
+ * @example
97
+ * // Behind Cloudflare
98
+ * const ip = detectClientIp(req, { platform: 'cloudflare' });
89
99
  *
90
- * // Allow safe redirects
91
- * validateRedirect('/dashboard') // { safe: true }
92
- * validateRedirect('/users?page=2') // { safe: true }
93
- * validateRedirect('https://myapp.com/home', { allowedHosts: ['myapp.com'] }) // { safe: true }
100
+ * @example
101
+ * // Behind 2 proxies (e.g. CDN + load balancer)
102
+ * const ip = detectClientIp(req, { trustedProxyCount: 2 });
94
103
  */
95
- /** Options for redirect validation */
96
- interface ValidateRedirectOptions {
97
- /** Hostnames that are allowed for absolute URL redirects */
98
- allowedHosts?: string[];
99
- /** Allow protocol-relative URLs (//example.com). Default: false */
100
- allowProtocolRelative?: boolean;
101
- /** Allowed protocols for absolute URLs. Default: ['http:', 'https:'] */
102
- allowedProtocols?: string[];
103
- }
104
- /** Result of redirect validation */
105
- interface ValidateRedirectResult {
106
- /** Whether the redirect URL is safe */
107
- safe: boolean;
108
- /** Reason the redirect was blocked (only set when safe=false) */
109
- reason?: string;
110
- }
104
+ declare function detectClientIp(req: RequestLike$1 | IncomingMessage, options?: DetectIpOptions): string;
111
105
  /**
112
- * Validate a redirect URL to prevent open redirect attacks.
113
- *
114
- * Safe redirects:
115
- * - Relative paths: /dashboard, /users?page=2, ../settings
116
- * - Absolute URLs to allowed hosts (when configured)
117
- *
118
- * Blocked redirects:
119
- * - Absolute URLs to unknown hosts
120
- * - Protocol-relative URLs (//evil.com)
121
- * - javascript:, data:, vbscript:, blob: protocols
122
- * - Backslash-prefixed paths (\\evil.com — browser treats as //)
123
- * - URLs with control characters that could disguise the target
124
- *
125
- * @param url - The redirect target URL to validate
126
- * @param options - Validation options
127
- * @returns Validation result with safe flag and optional reason
106
+ * Check if an IP address is a private/internal address.
107
+ *
108
+ * Detects: loopback, private ranges (RFC 1918), link-local, IPv6 equivalents.
128
109
  */
129
- declare function validateRedirect(url: string, options?: ValidateRedirectOptions): ValidateRedirectResult;
110
+ declare function isPrivateIp(ip: string): boolean;
111
+
130
112
  /**
131
- * Convenience wrapper that returns true/false.
113
+ * @module @arcis/node/utils/fingerprint
114
+ * Deterministic request fingerprinting via SHA-256.
115
+ *
116
+ * Generates a stable hash from request characteristics for
117
+ * rate limiting keys, abuse detection, and analytics.
132
118
  *
133
- * @param url - The redirect URL to check
134
- * @param options - Validation options
135
- * @returns true if the redirect is safe
119
+ * @example
120
+ * const fp = await fingerprint(req);
121
+ * // "a3f2b8c1d4e5..."
122
+ */
123
+
124
+ interface FingerprintOptions {
125
+ /** Include IP address in fingerprint. Default: true */
126
+ ip?: boolean;
127
+ /** Include User-Agent header. Default: true */
128
+ userAgent?: boolean;
129
+ /** Include Accept header. Default: true */
130
+ accept?: boolean;
131
+ /** Include Accept-Language header. Default: true */
132
+ acceptLanguage?: boolean;
133
+ /** Include Accept-Encoding header. Default: true */
134
+ acceptEncoding?: boolean;
135
+ /** Additional custom components to include */
136
+ custom?: string[];
137
+ /** IP detection options */
138
+ ipOptions?: DetectIpOptions;
139
+ }
140
+ interface RequestLike {
141
+ headers: Record<string, string | string[] | undefined>;
142
+ socket?: {
143
+ remoteAddress?: string;
144
+ };
145
+ connection?: {
146
+ remoteAddress?: string;
147
+ };
148
+ ip?: string;
149
+ }
150
+ /**
151
+ * Generate a deterministic fingerprint for a request.
152
+ *
153
+ * Creates a SHA-256 hash from configurable request components.
154
+ * The fingerprint is stable across requests from the same client
155
+ * (same IP, browser, language settings).
156
+ *
157
+ * @param req - HTTP request object
158
+ * @param options - Fingerprint configuration
159
+ * @returns Hex-encoded SHA-256 hash (64 characters)
160
+ *
161
+ * @example
162
+ * // Default fingerprint (IP + UA + Accept headers)
163
+ * const fp = fingerprint(req);
164
+ *
165
+ * @example
166
+ * // IP-only fingerprint (for simple rate limiting)
167
+ * const fp = fingerprint(req, { userAgent: false, accept: false, acceptLanguage: false, acceptEncoding: false });
168
+ *
169
+ * @example
170
+ * // With custom components
171
+ * const fp = fingerprint(req, { custom: [req.body?.userId] });
136
172
  */
137
- declare function isRedirectSafe(url: string, options?: ValidateRedirectOptions): boolean;
173
+ declare function fingerprint(req: RequestLike, options?: FingerprintOptions): string;
138
174
 
139
- export { type ValidateRedirectOptions, type ValidateRedirectResult, type ValidateUrlOptions, type ValidateUrlResult, isRedirectSafe, isUrlSafe, validateRedirect, validateUrl };
175
+ export { type DetectIpOptions, type FingerprintOptions, type Platform, detectClientIp, fingerprint, formatDuration, isPrivateIp, parseDuration };