@roboteby/parry 1.1.0-rc.1
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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +284 -0
- package/config/defaults.js +65 -0
- package/constants/patterns.js +77 -0
- package/package.json +89 -0
- package/src/admin/admin-router.js +106 -0
- package/src/admin/auth/admin-auth.js +176 -0
- package/src/admin/auth/index.js +13 -0
- package/src/admin/auth/strategies/alb-auth.js +49 -0
- package/src/admin/auth/strategies/cloudflare-access.js +34 -0
- package/src/admin/auth/strategies/combined.js +50 -0
- package/src/admin/auth/strategies/ip-allowlist.js +13 -0
- package/src/admin/auth/strategies/none.js +20 -0
- package/src/admin/auth/strategies/token.js +25 -0
- package/src/admin/auth/strategies/trusted-proxy.js +52 -0
- package/src/admin/auth/utils/constant-time.js +18 -0
- package/src/admin/auth/utils/external-identity.js +156 -0
- package/src/admin/auth/utils/header-utils.js +39 -0
- package/src/admin/auth/utils/result.js +39 -0
- package/src/admin/ban-normalizer.js +98 -0
- package/src/admin/index.js +12 -0
- package/src/admin/response.js +41 -0
- package/src/brute-force/brute-force-guard.js +268 -0
- package/src/brute-force/index.js +32 -0
- package/src/brute-force/key-builder.js +164 -0
- package/src/brute-force/result.js +35 -0
- package/src/core/engine.js +264 -0
- package/src/core/index.js +7 -0
- package/src/core/logger.js +3 -0
- package/src/core/rate-limit-result.js +13 -0
- package/src/core/rateLimiter.js +3 -0
- package/src/core/scoring.js +18 -0
- package/src/core/threat-event.js +69 -0
- package/src/detectors/hpp.js +30 -0
- package/src/detectors/index.js +19 -0
- package/src/detectors/nosql.js +53 -0
- package/src/detectors/path-traversal.js +72 -0
- package/src/detectors/prototype-pollution.js +69 -0
- package/src/detectors/request-shape.js +76 -0
- package/src/detectors/sql.js +18 -0
- package/src/detectors/xss.js +18 -0
- package/src/events/event-bus.js +51 -0
- package/src/events/index.js +19 -0
- package/src/events/memory-event-store.js +64 -0
- package/src/events/sanitize-event.js +54 -0
- package/src/events/threat-event.js +174 -0
- package/src/express/ip-resolver.js +109 -0
- package/src/express/middleware.js +379 -0
- package/src/express/request-targets.js +35 -0
- package/src/express/response.js +14 -0
- package/src/index.js +41 -0
- package/src/logger/console-reporter.js +75 -0
- package/src/middleware/index.js +7 -0
- package/src/middleware/parry_ddos.js +3 -0
- package/src/observability/index.js +6 -0
- package/src/observability/metrics.js +61 -0
- package/src/observability/snapshot.js +48 -0
- package/src/policies/index.js +15 -0
- package/src/policies/matcher.js +48 -0
- package/src/policies/normalize-policy.js +94 -0
- package/src/policies/presets.js +34 -0
- package/src/rate-limit/keys.js +7 -0
- package/src/rate-limit/limiter.js +124 -0
- package/src/stores/README.md +51 -0
- package/src/stores/index.js +6 -0
- package/src/stores/memory-store.js +278 -0
- package/src/stores/redis-store.js +349 -0
- package/src/utils/decode.js +56 -0
- package/src/utils/flatten.js +27 -0
- package/src/utils/normalize.js +21 -0
- package/types/index.d.ts +555 -0
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { Request, Response, RequestHandler, Router } from 'express';
|
|
2
|
+
|
|
3
|
+
export interface Parry_DDoSOptions {
|
|
4
|
+
/** Enables SQL injection detection. Default: true */
|
|
5
|
+
sql?: boolean;
|
|
6
|
+
/** Enables XSS detection. Default: true */
|
|
7
|
+
xss?: boolean;
|
|
8
|
+
/** Enables NoSQL injection detection. Default: true */
|
|
9
|
+
nosql?: boolean;
|
|
10
|
+
/** HTTP Parameter Pollution protection. Default: disabled */
|
|
11
|
+
hpp?: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
allowDuplicateParamsFor?: string[];
|
|
14
|
+
};
|
|
15
|
+
/** Prototype Pollution key protection. Default: enabled */
|
|
16
|
+
prototypePollution?: {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Path Traversal protection for request values. Default: enabled */
|
|
20
|
+
pathTraversal?: {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** Request shape limits. Default: enabled with conservative limits */
|
|
24
|
+
requestShape?: {
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
maxDepth?: number;
|
|
27
|
+
maxKeys?: number;
|
|
28
|
+
maxArrayLength?: number;
|
|
29
|
+
maxStringLength?: number;
|
|
30
|
+
};
|
|
31
|
+
/** Enables rate limiting by IP. Default: true */
|
|
32
|
+
rateLimit?:
|
|
33
|
+
| boolean
|
|
34
|
+
| {
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
max?: number;
|
|
37
|
+
maxRequests?: number;
|
|
38
|
+
windowMs?: number;
|
|
39
|
+
headers?: boolean;
|
|
40
|
+
};
|
|
41
|
+
/** Maximum number of requests per time window per IP. Default: 100 */
|
|
42
|
+
maxRequests?: number;
|
|
43
|
+
/** Duration of the rate limiting window in ms. Default: 60000 */
|
|
44
|
+
windowMs?: number;
|
|
45
|
+
/** Shared rate limit store. Defaults to MemoryStore. */
|
|
46
|
+
store?: RateLimitStore;
|
|
47
|
+
/** Store error behavior. Default: fail-open */
|
|
48
|
+
storeFailureMode?: 'fail-open' | 'fail-closed';
|
|
49
|
+
/** Optional route-based policies. */
|
|
50
|
+
policies?: PolicyConfig[];
|
|
51
|
+
/** Optional policy preset. Default: off */
|
|
52
|
+
preset?: 'off' | 'recommended' | 'strict';
|
|
53
|
+
/** Global brute force switch. Default: disabled */
|
|
54
|
+
bruteForce?: false | { enabled?: boolean };
|
|
55
|
+
/** Recent event buffer configuration. Default: { maxEvents: 500 } */
|
|
56
|
+
events?: {
|
|
57
|
+
maxEvents?: number;
|
|
58
|
+
};
|
|
59
|
+
/** Admin API metadata. The router is never mounted automatically. */
|
|
60
|
+
admin?: {
|
|
61
|
+
enabled?: boolean;
|
|
62
|
+
path?: string;
|
|
63
|
+
allowMutations?: boolean;
|
|
64
|
+
allowInsecureAdminApi?: boolean;
|
|
65
|
+
auth?: AdminAuthConfig;
|
|
66
|
+
};
|
|
67
|
+
/** Request id configuration. Default: enabled with x-request-id input and no response header. */
|
|
68
|
+
requestId?: {
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
header?: string;
|
|
71
|
+
responseHeader?: false | string;
|
|
72
|
+
};
|
|
73
|
+
/** Trust x-forwarded-for only when the direct peer matches trustedProxies. Default: false */
|
|
74
|
+
trustProxyHeaders?: boolean;
|
|
75
|
+
/** Proxy IPs or CIDRs allowed to provide forwarded client IP headers. */
|
|
76
|
+
trustedProxies?: string[];
|
|
77
|
+
/** Emits extra internal observability events where supported. Default: false */
|
|
78
|
+
debug?: boolean;
|
|
79
|
+
/** Suspicious attempts before temporary ban. Default: 5 */
|
|
80
|
+
suspiciousThreshold?: number;
|
|
81
|
+
/** Duration of the ban in ms. Default: 300000 (5 min) */
|
|
82
|
+
banDurationMs?: number;
|
|
83
|
+
/** Displays colored threat logs in the console. Default: true */
|
|
84
|
+
logThreats?: boolean;
|
|
85
|
+
/** Callback triggered for each detected threat */
|
|
86
|
+
onThreat?: (entry: ThreatEvent, req: Request, res: Response) => void;
|
|
87
|
+
/** Callback triggered for every emitted Parry event */
|
|
88
|
+
onEvent?: (event: ThreatEvent) => void;
|
|
89
|
+
/** Callback triggered when a configured store throws */
|
|
90
|
+
onStoreError?: (error: Error, event: ThreatEvent) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ThreatSeverity = 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
94
|
+
export type ThreatAction = 'allowed' | 'blocked' | 'observed' | 'reset' | 'error' | 'created';
|
|
95
|
+
|
|
96
|
+
export type DetectorType =
|
|
97
|
+
| 'SQL_INJECTION'
|
|
98
|
+
| 'XSS'
|
|
99
|
+
| 'NOSQL_INJECTION'
|
|
100
|
+
| 'HTTP_PARAMETER_POLLUTION'
|
|
101
|
+
| 'PROTOTYPE_POLLUTION'
|
|
102
|
+
| 'PATH_TRAVERSAL'
|
|
103
|
+
| 'REQUEST_SHAPE'
|
|
104
|
+
| 'BRUTE_FORCE'
|
|
105
|
+
| 'ROUTE_RATE_LIMIT';
|
|
106
|
+
|
|
107
|
+
export interface ThreatMatch {
|
|
108
|
+
detector: DetectorType;
|
|
109
|
+
field: string;
|
|
110
|
+
pattern: string;
|
|
111
|
+
reason?: string;
|
|
112
|
+
severity?: ThreatSeverity;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type LogEntryType =
|
|
116
|
+
| 'THREAT'
|
|
117
|
+
| 'BAN'
|
|
118
|
+
| 'RATE_LIMIT'
|
|
119
|
+
| 'STORE_FAILURE'
|
|
120
|
+
| 'BRUTE_FORCE_ATTEMPT'
|
|
121
|
+
| 'BRUTE_FORCE_BLOCK'
|
|
122
|
+
| 'BRUTE_FORCE_RESET'
|
|
123
|
+
| 'ROUTE_RATE_LIMIT_EXCEEDED';
|
|
124
|
+
|
|
125
|
+
export type ThreatEventType =
|
|
126
|
+
| 'SQL_INJECTION_BLOCKED'
|
|
127
|
+
| 'XSS_BLOCKED'
|
|
128
|
+
| 'NOSQL_INJECTION_BLOCKED'
|
|
129
|
+
| 'HPP_BLOCKED'
|
|
130
|
+
| 'PROTOTYPE_POLLUTION_BLOCKED'
|
|
131
|
+
| 'PATH_TRAVERSAL_BLOCKED'
|
|
132
|
+
| 'REQUEST_SHAPE_BLOCKED'
|
|
133
|
+
| 'RATE_LIMIT_EXCEEDED'
|
|
134
|
+
| 'ROUTE_RATE_LIMIT_EXCEEDED'
|
|
135
|
+
| 'TEMPORARY_BAN_CREATED'
|
|
136
|
+
| 'TEMPORARY_BAN_HIT'
|
|
137
|
+
| 'BRUTE_FORCE_ATTEMPT'
|
|
138
|
+
| 'BRUTE_FORCE_BLOCKED'
|
|
139
|
+
| 'BRUTE_FORCE_RESET'
|
|
140
|
+
| 'STORE_ERROR'
|
|
141
|
+
| 'HOOK_ERROR'
|
|
142
|
+
| 'SECURITY_EVENT';
|
|
143
|
+
|
|
144
|
+
export interface ThreatLogEntry {
|
|
145
|
+
id?: string;
|
|
146
|
+
type: LogEntryType | ThreatEventType | string;
|
|
147
|
+
ip: string;
|
|
148
|
+
timestamp: string;
|
|
149
|
+
method?: string;
|
|
150
|
+
url?: string;
|
|
151
|
+
path?: string;
|
|
152
|
+
detector?: string;
|
|
153
|
+
detectorType?: DetectorType | string;
|
|
154
|
+
detectorSlug?: string;
|
|
155
|
+
severity?: ThreatSeverity;
|
|
156
|
+
action?: ThreatAction;
|
|
157
|
+
statusCode?: number;
|
|
158
|
+
target?: string;
|
|
159
|
+
reason?: string;
|
|
160
|
+
module?: string;
|
|
161
|
+
policyName?: string;
|
|
162
|
+
keyTypes?: string[];
|
|
163
|
+
requestId?: string;
|
|
164
|
+
userAgent?: string;
|
|
165
|
+
metadata?: Record<string, unknown>;
|
|
166
|
+
threats?: ThreatMatch[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ThreatEvent extends ThreatLogEntry {
|
|
170
|
+
id: string;
|
|
171
|
+
timestamp: string;
|
|
172
|
+
severity: ThreatSeverity;
|
|
173
|
+
action: ThreatAction;
|
|
174
|
+
metadata: Record<string, unknown>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface PolicyConfig {
|
|
178
|
+
name: string;
|
|
179
|
+
match: {
|
|
180
|
+
method?: string | string[];
|
|
181
|
+
path?: string | string[] | RegExp;
|
|
182
|
+
};
|
|
183
|
+
inheritGlobalRateLimit?: boolean;
|
|
184
|
+
rateLimit?: {
|
|
185
|
+
enabled?: boolean;
|
|
186
|
+
max?: number;
|
|
187
|
+
maxRequests?: number;
|
|
188
|
+
windowMs?: number;
|
|
189
|
+
key?: 'ip' | 'ip+path' | ((requestData: unknown) => string | { type?: string; value: string } | null);
|
|
190
|
+
};
|
|
191
|
+
bruteForce?: {
|
|
192
|
+
enabled?: boolean;
|
|
193
|
+
maxAttempts?: number;
|
|
194
|
+
windowMs?: number;
|
|
195
|
+
blockDurationMs?: number;
|
|
196
|
+
keys?: Array<string | ((requestData: unknown) => string | { type?: string; value: string } | null)>;
|
|
197
|
+
failureStatusCodes?: number[];
|
|
198
|
+
successStatusCodes?: number[];
|
|
199
|
+
blockedStatusCode?: number;
|
|
200
|
+
resetOnSuccess?: boolean;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface ParryRequestContext {
|
|
205
|
+
requestId?: string;
|
|
206
|
+
recordAuthFailure(reason?: string): void;
|
|
207
|
+
recordAuthSuccess(): void;
|
|
208
|
+
[key: string]: unknown;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface EventPage {
|
|
212
|
+
data: ThreatEvent[];
|
|
213
|
+
pagination: {
|
|
214
|
+
limit: number;
|
|
215
|
+
offset: number;
|
|
216
|
+
total: number;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface EventFilters {
|
|
221
|
+
limit?: number | string;
|
|
222
|
+
offset?: number | string;
|
|
223
|
+
type?: string;
|
|
224
|
+
severity?: ThreatSeverity | string;
|
|
225
|
+
action?: ThreatAction | string;
|
|
226
|
+
detector?: string;
|
|
227
|
+
ip?: string;
|
|
228
|
+
path?: string;
|
|
229
|
+
policyName?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface MetricsSnapshot {
|
|
233
|
+
startedAt: string;
|
|
234
|
+
uptimeMs: number;
|
|
235
|
+
totalRequests: number;
|
|
236
|
+
allowedRequests: number;
|
|
237
|
+
blockedRequests: number;
|
|
238
|
+
rateLimitedRequests: number;
|
|
239
|
+
bruteForceBlocks: number;
|
|
240
|
+
activeBans: number;
|
|
241
|
+
eventsByType: Record<string, number>;
|
|
242
|
+
eventsBySeverity: Record<string, number>;
|
|
243
|
+
eventsByDetector: Record<string, number>;
|
|
244
|
+
eventsByAction: Record<string, number>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export type AdminAuthMode =
|
|
248
|
+
| 'none'
|
|
249
|
+
| 'token'
|
|
250
|
+
| 'ip-allowlist'
|
|
251
|
+
| 'trusted-proxy'
|
|
252
|
+
| 'cloudflare-access'
|
|
253
|
+
| 'alb-auth'
|
|
254
|
+
| 'cognito-alb'
|
|
255
|
+
| 'combined';
|
|
256
|
+
|
|
257
|
+
export interface AdminTokenAuthConfig {
|
|
258
|
+
mode: 'token';
|
|
259
|
+
token: string;
|
|
260
|
+
header?: string;
|
|
261
|
+
trustProxyHeaders?: boolean;
|
|
262
|
+
trustedProxies?: string[];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface AdminIpAllowlistAuthConfig {
|
|
266
|
+
mode: 'ip-allowlist';
|
|
267
|
+
allowedIps: string[];
|
|
268
|
+
trustProxyHeaders?: boolean;
|
|
269
|
+
trustedProxies?: string[];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface AdminTrustedProxyAuthConfig {
|
|
273
|
+
mode: 'trusted-proxy';
|
|
274
|
+
trustedProxies: string[];
|
|
275
|
+
requiredHeaders?: Record<string, string>;
|
|
276
|
+
userHeader?: string;
|
|
277
|
+
emailHeader?: string;
|
|
278
|
+
rolesHeader?: string;
|
|
279
|
+
proxySharedSecretHeader?: string;
|
|
280
|
+
proxySharedSecret?: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface AdminExternalAuthBoundaryConfig {
|
|
284
|
+
trustedProxies?: string[];
|
|
285
|
+
proxySharedSecretHeader?: string;
|
|
286
|
+
proxySharedSecret?: string;
|
|
287
|
+
allowedEmails?: string[];
|
|
288
|
+
allowedDomains?: string[];
|
|
289
|
+
verifyJwt?: boolean;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface AdminCloudflareAccessAuthConfig extends AdminExternalAuthBoundaryConfig {
|
|
293
|
+
mode: 'cloudflare-access';
|
|
294
|
+
emailHeader?: string;
|
|
295
|
+
jwtHeader?: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface AdminAlbAuthConfig extends AdminExternalAuthBoundaryConfig {
|
|
299
|
+
mode: 'alb-auth' | 'cognito-alb';
|
|
300
|
+
userHeader?: string;
|
|
301
|
+
dataHeader?: string;
|
|
302
|
+
emailHeader?: string;
|
|
303
|
+
allowedSubjects?: string[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface AdminNoneAuthConfig {
|
|
307
|
+
mode: 'none';
|
|
308
|
+
allowInsecureAdminApi?: boolean;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export type AdminAuthStrategyConfig =
|
|
312
|
+
| AdminTokenAuthConfig
|
|
313
|
+
| AdminIpAllowlistAuthConfig
|
|
314
|
+
| AdminTrustedProxyAuthConfig
|
|
315
|
+
| AdminCloudflareAccessAuthConfig
|
|
316
|
+
| AdminAlbAuthConfig
|
|
317
|
+
| AdminNoneAuthConfig;
|
|
318
|
+
|
|
319
|
+
export interface AdminCombinedAuthConfig {
|
|
320
|
+
mode: 'combined';
|
|
321
|
+
allowAny?: AdminAuthStrategyConfig[];
|
|
322
|
+
requireAll?: AdminAuthStrategyConfig[];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export type AdminAuthConfig = AdminAuthStrategyConfig | AdminCombinedAuthConfig;
|
|
326
|
+
|
|
327
|
+
export interface ParryAdminContext {
|
|
328
|
+
authenticated: true;
|
|
329
|
+
strategy: AdminAuthMode | 'callback';
|
|
330
|
+
subject: string;
|
|
331
|
+
email: string | null;
|
|
332
|
+
roles: string[];
|
|
333
|
+
ip: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface AdminRouterOptions {
|
|
337
|
+
requireAuth?: boolean;
|
|
338
|
+
auth?: ((req: Request) => boolean | Promise<boolean>) | AdminAuthConfig;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface ParryInstance {
|
|
342
|
+
middleware(): RequestHandler;
|
|
343
|
+
eventBus: EventBus;
|
|
344
|
+
metrics: Metrics;
|
|
345
|
+
eventStore: MemoryEventStore;
|
|
346
|
+
store: RateLimitStore;
|
|
347
|
+
policies: PolicyConfig[];
|
|
348
|
+
getContext(): unknown;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface RateLimitResult {
|
|
352
|
+
limited: boolean;
|
|
353
|
+
banned: boolean;
|
|
354
|
+
remaining: number;
|
|
355
|
+
resetAt: number;
|
|
356
|
+
banExpiresAt: number | null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface StoreCounterResult {
|
|
360
|
+
key: string;
|
|
361
|
+
count: number;
|
|
362
|
+
resetAt: number | null;
|
|
363
|
+
ttlMs: number;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export interface StoreBanResult {
|
|
367
|
+
key: string;
|
|
368
|
+
banned: boolean;
|
|
369
|
+
banExpiresAt: number | null;
|
|
370
|
+
createdAt?: number;
|
|
371
|
+
metadata?: unknown;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export interface RateLimitStore {
|
|
375
|
+
incrementRateLimit(key: string, windowMs: number): StoreCounterResult | Promise<StoreCounterResult>;
|
|
376
|
+
getRateLimit(key: string): StoreCounterResult | Promise<StoreCounterResult>;
|
|
377
|
+
resetRateLimit(key: string): unknown;
|
|
378
|
+
ban(key: string, ttlMs: number, metadata?: unknown): StoreBanResult | Promise<StoreBanResult>;
|
|
379
|
+
isBanned(key: string): StoreBanResult | Promise<StoreBanResult>;
|
|
380
|
+
unban(key: string): unknown;
|
|
381
|
+
recordSuspicious(
|
|
382
|
+
key: string,
|
|
383
|
+
ttlMs: number,
|
|
384
|
+
metadata?: unknown
|
|
385
|
+
): StoreCounterResult | Promise<StoreCounterResult>;
|
|
386
|
+
incrementCounter(key: string, ttlMs: number, metadata?: unknown): StoreCounterResult | Promise<StoreCounterResult>;
|
|
387
|
+
getCounter(key: string): StoreCounterResult | Promise<StoreCounterResult>;
|
|
388
|
+
resetCounter(key: string): unknown;
|
|
389
|
+
blockKey(key: string, ttlMs: number, metadata?: unknown): StoreBlockResult | Promise<StoreBlockResult>;
|
|
390
|
+
isBlocked(key: string): StoreBlockResult | Promise<StoreBlockResult>;
|
|
391
|
+
unblockKey(key: string): unknown;
|
|
392
|
+
listBans?(options?: unknown): BanSnapshot[] | Promise<BanSnapshot[]>;
|
|
393
|
+
listBlocks?(options?: unknown): BlockSnapshot[] | Promise<BlockSnapshot[]>;
|
|
394
|
+
getStoreInfo?(): unknown;
|
|
395
|
+
close?(): unknown;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface StoreBlockResult {
|
|
399
|
+
key: string;
|
|
400
|
+
blocked: boolean;
|
|
401
|
+
blockExpiresAt: number | null;
|
|
402
|
+
createdAt?: number;
|
|
403
|
+
metadata?: unknown;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export interface IPSnapshot {
|
|
407
|
+
ip: string;
|
|
408
|
+
requests: number;
|
|
409
|
+
suspicious: number;
|
|
410
|
+
banned: boolean;
|
|
411
|
+
banExpiresAt: number | null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface BanSnapshot {
|
|
415
|
+
key: string;
|
|
416
|
+
createdAt?: number;
|
|
417
|
+
banExpiresAt: number;
|
|
418
|
+
ttlMs?: number;
|
|
419
|
+
metadata?: unknown;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface BlockSnapshot {
|
|
423
|
+
key: string;
|
|
424
|
+
createdAt?: number;
|
|
425
|
+
blockExpiresAt: number;
|
|
426
|
+
ttlMs?: number;
|
|
427
|
+
metadata?: unknown;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export declare class RateLimiter {
|
|
431
|
+
constructor(
|
|
432
|
+
config: Pick<
|
|
433
|
+
Parry_DDoSOptions,
|
|
434
|
+
'rateLimit' | 'maxRequests' | 'windowMs' | 'suspiciousThreshold' | 'banDurationMs' | 'store'
|
|
435
|
+
>,
|
|
436
|
+
store?: RateLimitStore
|
|
437
|
+
);
|
|
438
|
+
check(ip: string): Promise<RateLimitResult>;
|
|
439
|
+
recordSuspicious(ip: string): Promise<unknown>;
|
|
440
|
+
unban(ip: string): Promise<unknown>;
|
|
441
|
+
snapshot(): Promise<IPSnapshot[]>;
|
|
442
|
+
destroy(): unknown;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export declare class MemoryStore implements RateLimitStore {
|
|
446
|
+
constructor();
|
|
447
|
+
incrementRateLimit(key: string, windowMs: number): StoreCounterResult;
|
|
448
|
+
getRateLimit(key: string): StoreCounterResult;
|
|
449
|
+
resetRateLimit(key: string): boolean;
|
|
450
|
+
ban(key: string, ttlMs: number, metadata?: unknown): StoreBanResult;
|
|
451
|
+
isBanned(key: string): StoreBanResult;
|
|
452
|
+
unban(key: string): boolean;
|
|
453
|
+
recordSuspicious(key: string, ttlMs: number, metadata?: unknown): StoreCounterResult;
|
|
454
|
+
incrementCounter(key: string, ttlMs: number, metadata?: unknown): StoreCounterResult;
|
|
455
|
+
getCounter(key: string): StoreCounterResult;
|
|
456
|
+
resetCounter(key: string): boolean;
|
|
457
|
+
blockKey(key: string, ttlMs: number, metadata?: unknown): StoreBlockResult;
|
|
458
|
+
isBlocked(key: string): StoreBlockResult;
|
|
459
|
+
unblockKey(key: string): boolean;
|
|
460
|
+
cleanup(now?: number): void;
|
|
461
|
+
snapshot(windowMs: number): IPSnapshot[];
|
|
462
|
+
listBans(): BanSnapshot[];
|
|
463
|
+
listBlocks(): BlockSnapshot[];
|
|
464
|
+
getStoreInfo(): unknown;
|
|
465
|
+
clear(): void;
|
|
466
|
+
close(): void;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export declare class RedisStore implements RateLimitStore {
|
|
470
|
+
constructor(options: { client: unknown; prefix?: string; closeClient?: boolean });
|
|
471
|
+
incrementRateLimit(key: string, windowMs: number): Promise<StoreCounterResult>;
|
|
472
|
+
getRateLimit(key: string): Promise<StoreCounterResult>;
|
|
473
|
+
resetRateLimit(key: string): Promise<unknown>;
|
|
474
|
+
ban(key: string, ttlMs: number, metadata?: unknown): Promise<StoreBanResult>;
|
|
475
|
+
isBanned(key: string): Promise<StoreBanResult>;
|
|
476
|
+
unban(key: string): Promise<unknown>;
|
|
477
|
+
recordSuspicious(key: string, ttlMs: number, metadata?: unknown): Promise<StoreCounterResult>;
|
|
478
|
+
incrementCounter(key: string, ttlMs: number, metadata?: unknown): Promise<StoreCounterResult>;
|
|
479
|
+
getCounter(key: string): Promise<StoreCounterResult>;
|
|
480
|
+
resetCounter(key: string): Promise<unknown>;
|
|
481
|
+
blockKey(key: string, ttlMs: number, metadata?: unknown): Promise<StoreBlockResult>;
|
|
482
|
+
isBlocked(key: string): Promise<StoreBlockResult>;
|
|
483
|
+
unblockKey(key: string): Promise<unknown>;
|
|
484
|
+
listBans(): Promise<BanSnapshot[]>;
|
|
485
|
+
listBlocks(): Promise<BlockSnapshot[]>;
|
|
486
|
+
getStoreInfo(): unknown;
|
|
487
|
+
close(): Promise<unknown>;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export declare const SQLInjectionDetector: {
|
|
491
|
+
scan(value: string): string | null;
|
|
492
|
+
};
|
|
493
|
+
export declare const XSSDetector: { scan(value: string): string | null };
|
|
494
|
+
export declare const NoSQLDetector: { scan(value: unknown): string | null };
|
|
495
|
+
export declare const HPPDetector: {
|
|
496
|
+
scan(
|
|
497
|
+
query: unknown,
|
|
498
|
+
options?: { allowDuplicateParamsFor?: string[] }
|
|
499
|
+
): ThreatMatch | null;
|
|
500
|
+
};
|
|
501
|
+
export declare const PrototypePollutionDetector: {
|
|
502
|
+
scan(surfaces: unknown): ThreatMatch | null;
|
|
503
|
+
};
|
|
504
|
+
export declare const PathTraversalDetector: {
|
|
505
|
+
scan(targets: Array<{ label: string; value: unknown }>): ThreatMatch | null;
|
|
506
|
+
};
|
|
507
|
+
export declare const RequestShapeGuard: {
|
|
508
|
+
scan(
|
|
509
|
+
surfaces: unknown,
|
|
510
|
+
options: {
|
|
511
|
+
maxDepth: number;
|
|
512
|
+
maxKeys: number;
|
|
513
|
+
maxArrayLength: number;
|
|
514
|
+
maxStringLength: number;
|
|
515
|
+
}
|
|
516
|
+
): ThreatMatch | null;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
export declare class MemoryEventStore {
|
|
520
|
+
constructor(options?: { maxEvents?: number });
|
|
521
|
+
add(event: ThreatEvent): ThreatEvent;
|
|
522
|
+
getRecentEvents(options?: EventFilters): EventPage;
|
|
523
|
+
getById(id: string): ThreatEvent | null;
|
|
524
|
+
clear(): void;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export declare class EventBus {
|
|
528
|
+
constructor(options?: { eventStore?: MemoryEventStore; maxEvents?: number });
|
|
529
|
+
emitThreat(event: Partial<ThreatEvent> | ThreatLogEntry, context?: { req?: Request; res?: Response }): ThreatEvent;
|
|
530
|
+
onThreat(listener: (event: ThreatEvent, req?: Request, res?: Response) => void): () => void;
|
|
531
|
+
getRecentEvents(options?: EventFilters): EventPage;
|
|
532
|
+
getEventById(id: string): ThreatEvent | null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export declare class Metrics {
|
|
536
|
+
constructor();
|
|
537
|
+
increment(name: string, value?: number): void;
|
|
538
|
+
recordRequest(action: 'started' | 'allowed' | 'blocked'): void;
|
|
539
|
+
recordEvent(event: ThreatEvent): void;
|
|
540
|
+
snapshot(extra?: { activeBans?: number }): MetricsSnapshot;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export declare function Parry_DDoS(options?: Parry_DDoSOptions): RequestHandler;
|
|
544
|
+
export declare function createParry(options?: Parry_DDoSOptions): ParryInstance;
|
|
545
|
+
export declare function createParryAdminRouter(
|
|
546
|
+
parry: ParryInstance | RequestHandler,
|
|
547
|
+
options?: AdminRouterOptions
|
|
548
|
+
): Router;
|
|
549
|
+
|
|
550
|
+
declare module 'express-serve-static-core' {
|
|
551
|
+
interface Request {
|
|
552
|
+
parry?: ParryRequestContext;
|
|
553
|
+
parryAdmin?: ParryAdminContext;
|
|
554
|
+
}
|
|
555
|
+
}
|