@roboteby/parry 1.1.1 → 2.0.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/types/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import { Request, Response, RequestHandler, Router } from 'express';
2
2
 
3
- export interface Parry_DDoSOptions {
3
+ export interface ParryOptions {
4
4
  /** Enables SQL injection detection. Default: true */
5
5
  sql?: boolean;
6
6
  /** Enables XSS detection. Default: true */
7
7
  xss?: boolean;
8
- /** Enables NoSQL injection detection. Default: true */
9
- nosql?: boolean;
8
+ /** Enables NoSQL injection detection and exact-path operator allowlists. Default: true */
9
+ nosql?:
10
+ | boolean
11
+ | {
12
+ enabled?: boolean;
13
+ allowedOperators?: Record<string, string[]>;
14
+ };
10
15
  /** HTTP Parameter Pollution protection. Default: disabled */
11
16
  hpp?: {
12
17
  enabled?: boolean;
@@ -74,6 +79,10 @@ export interface Parry_DDoSOptions {
74
79
  trustProxyHeaders?: boolean;
75
80
  /** Proxy IPs or CIDRs allowed to provide forwarded client IP headers. */
76
81
  trustedProxies?: string[];
82
+ /** Scalar request headers scanned by SQLi/XSS/path detectors. Use [] to disable. */
83
+ headers?: {
84
+ scan?: string[];
85
+ };
77
86
  /** Emits extra internal observability events where supported. Default: false */
78
87
  debug?: boolean;
79
88
  /** Suspicious attempts before temporary ban. Default: 5 */
@@ -90,6 +99,9 @@ export interface Parry_DDoSOptions {
90
99
  onStoreError?: (error: Error, event: ThreatEvent) => void;
91
100
  }
92
101
 
102
+ /** @deprecated Use ParryOptions instead. */
103
+ export type Parry_DDoSOptions = ParryOptions;
104
+
93
105
  export type ThreatSeverity = 'none' | 'low' | 'medium' | 'high' | 'critical';
94
106
  export type ThreatAction = 'allowed' | 'blocked' | 'observed' | 'reset' | 'error' | 'created';
95
107
 
@@ -186,14 +198,19 @@ export interface PolicyConfig {
186
198
  max?: number;
187
199
  maxRequests?: number;
188
200
  windowMs?: number;
189
- key?: 'ip' | 'ip+path' | ((requestData: unknown) => string | { type?: string; value: string } | null);
201
+ key?:
202
+ | 'ip'
203
+ | 'ip+path'
204
+ | ((requestData: unknown) => string | { type?: string; value: string } | null);
190
205
  };
191
206
  bruteForce?: {
192
207
  enabled?: boolean;
193
208
  maxAttempts?: number;
194
209
  windowMs?: number;
195
210
  blockDurationMs?: number;
196
- keys?: Array<string | ((requestData: unknown) => string | { type?: string; value: string } | null)>;
211
+ keys?: Array<
212
+ string | ((requestData: unknown) => string | { type?: string; value: string } | null)
213
+ >;
197
214
  failureStatusCodes?: number[];
198
215
  successStatusCodes?: number[];
199
216
  blockedStatusCode?: number;
@@ -334,6 +351,9 @@ export interface ParryAdminContext {
334
351
  }
335
352
 
336
353
  export interface AdminRouterOptions {
354
+ /** Explicitly allows an unauthenticated Admin API outside production. Insecure. */
355
+ allowInsecureAdminApi?: boolean;
356
+ /** @deprecated Use allowInsecureAdminApi for explicit local-only anonymous access. */
337
357
  requireAuth?: boolean;
338
358
  auth?: ((req: Request) => boolean | Promise<boolean>) | AdminAuthConfig;
339
359
  }
@@ -372,7 +392,10 @@ export interface StoreBanResult {
372
392
  }
373
393
 
374
394
  export interface RateLimitStore {
375
- incrementRateLimit(key: string, windowMs: number): StoreCounterResult | Promise<StoreCounterResult>;
395
+ incrementRateLimit(
396
+ key: string,
397
+ windowMs: number
398
+ ): StoreCounterResult | Promise<StoreCounterResult>;
376
399
  getRateLimit(key: string): StoreCounterResult | Promise<StoreCounterResult>;
377
400
  resetRateLimit(key: string): unknown;
378
401
  ban(key: string, ttlMs: number, metadata?: unknown): StoreBanResult | Promise<StoreBanResult>;
@@ -383,10 +406,18 @@ export interface RateLimitStore {
383
406
  ttlMs: number,
384
407
  metadata?: unknown
385
408
  ): StoreCounterResult | Promise<StoreCounterResult>;
386
- incrementCounter(key: string, ttlMs: number, metadata?: unknown): StoreCounterResult | Promise<StoreCounterResult>;
409
+ incrementCounter(
410
+ key: string,
411
+ ttlMs: number,
412
+ metadata?: unknown
413
+ ): StoreCounterResult | Promise<StoreCounterResult>;
387
414
  getCounter(key: string): StoreCounterResult | Promise<StoreCounterResult>;
388
415
  resetCounter(key: string): unknown;
389
- blockKey(key: string, ttlMs: number, metadata?: unknown): StoreBlockResult | Promise<StoreBlockResult>;
416
+ blockKey(
417
+ key: string,
418
+ ttlMs: number,
419
+ metadata?: unknown
420
+ ): StoreBlockResult | Promise<StoreBlockResult>;
390
421
  isBlocked(key: string): StoreBlockResult | Promise<StoreBlockResult>;
391
422
  unblockKey(key: string): unknown;
392
423
  listBans?(options?: unknown): BanSnapshot[] | Promise<BanSnapshot[]>;
@@ -430,7 +461,7 @@ export interface BlockSnapshot {
430
461
  export declare class RateLimiter {
431
462
  constructor(
432
463
  config: Pick<
433
- Parry_DDoSOptions,
464
+ ParryOptions,
434
465
  'rateLimit' | 'maxRequests' | 'windowMs' | 'suspiciousThreshold' | 'banDurationMs' | 'store'
435
466
  >,
436
467
  store?: RateLimitStore
@@ -491,12 +522,18 @@ export declare const SQLInjectionDetector: {
491
522
  scan(value: string): string | null;
492
523
  };
493
524
  export declare const XSSDetector: { scan(value: string): string | null };
494
- export declare const NoSQLDetector: { scan(value: unknown): string | null };
495
- export declare const HPPDetector: {
525
+ export declare const NoSQLDetector: {
496
526
  scan(
497
- query: unknown,
498
- options?: { allowDuplicateParamsFor?: string[] }
499
- ): ThreatMatch | null;
527
+ value: unknown,
528
+ options?: { rootPath?: string; allowedOperators?: Record<string, string[]> }
529
+ ): string | null;
530
+ inspect(
531
+ value: unknown,
532
+ options?: { rootPath?: string; allowedOperators?: Record<string, string[]> }
533
+ ): { pattern: string; path: string } | null;
534
+ };
535
+ export declare const HPPDetector: {
536
+ scan(query: unknown, options?: { allowDuplicateParamsFor?: string[] }): ThreatMatch | null;
500
537
  };
501
538
  export declare const PrototypePollutionDetector: {
502
539
  scan(surfaces: unknown): ThreatMatch | null;
@@ -526,7 +563,10 @@ export declare class MemoryEventStore {
526
563
 
527
564
  export declare class EventBus {
528
565
  constructor(options?: { eventStore?: MemoryEventStore; maxEvents?: number });
529
- emitThreat(event: Partial<ThreatEvent> | ThreatLogEntry, context?: { req?: Request; res?: Response }): ThreatEvent;
566
+ emitThreat(
567
+ event: Partial<ThreatEvent> | ThreatLogEntry,
568
+ context?: { req?: Request; res?: Response }
569
+ ): ThreatEvent;
530
570
  onThreat(listener: (event: ThreatEvent, req?: Request, res?: Response) => void): () => void;
531
571
  getRecentEvents(options?: EventFilters): EventPage;
532
572
  getEventById(id: string): ThreatEvent | null;
@@ -540,8 +580,19 @@ export declare class Metrics {
540
580
  snapshot(extra?: { activeBans?: number }): MetricsSnapshot;
541
581
  }
542
582
 
543
- export declare function Parry_DDoS(options?: Parry_DDoSOptions): RequestHandler;
544
- export declare function createParry(options?: Parry_DDoSOptions): ParryInstance;
583
+ export declare class ThreatLogger {
584
+ constructor(enabled?: boolean);
585
+ log(entry: ThreatLogEntry): void;
586
+ logHookError(error: unknown, entry?: Partial<ThreatLogEntry>): void;
587
+ logStoreError(error: unknown, entry?: Partial<ThreatLogEntry>): void;
588
+ }
589
+
590
+ export declare const Policies: typeof import('./policies');
591
+ export declare const BruteForce: typeof import('./brute-force');
592
+
593
+ /** @deprecated Use createParry instead. */
594
+ export declare function Parry_DDoS(options?: ParryOptions): RequestHandler;
595
+ export declare function createParry(options?: ParryOptions): ParryInstance;
545
596
  export declare function createParryAdminRouter(
546
597
  parry: ParryInstance | RequestHandler,
547
598
  options?: AdminRouterOptions
@@ -0,0 +1,12 @@
1
+ export { Metrics, MetricsSnapshot, EventPage, PolicyConfig } from './index';
2
+ import { EventPage, MetricsSnapshot, PolicyConfig } from './index';
3
+
4
+ export declare function createSnapshot(context: Record<string, unknown>): {
5
+ metrics: MetricsSnapshot;
6
+ policies: PolicyConfig[];
7
+ store: string;
8
+ events: EventPage;
9
+ };
10
+ export declare function describeStore(store: unknown): string;
11
+ export declare function sanitizePolicies(policies: PolicyConfig[]): PolicyConfig[];
12
+ export declare function countActiveBans(store: unknown): number;
@@ -0,0 +1,21 @@
1
+ import { ParryOptions, PolicyConfig } from './index';
2
+
3
+ export declare function findMatchingPolicy(
4
+ policies: PolicyConfig[],
5
+ requestData: { method?: string; path?: string; url?: string }
6
+ ): PolicyConfig | null;
7
+ export declare function matchesPolicy(
8
+ policy: PolicyConfig,
9
+ requestData: { method?: string; path?: string; url?: string }
10
+ ): boolean;
11
+ export declare function matchesMethod(
12
+ expected: string | string[] | undefined,
13
+ method?: string
14
+ ): boolean;
15
+ export declare function matchesPath(
16
+ expected: string | string[] | RegExp | undefined,
17
+ path?: string
18
+ ): boolean;
19
+ export declare function buildPolicies(options?: ParryOptions): PolicyConfig[];
20
+ export declare function normalizePolicy(policy: PolicyConfig, options?: ParryOptions): PolicyConfig;
21
+ export declare function getPresetPolicies(name?: 'off' | 'recommended' | 'strict'): PolicyConfig[];
@@ -0,0 +1,10 @@
1
+ export {
2
+ MemoryStore,
3
+ RedisStore,
4
+ RateLimitStore,
5
+ StoreCounterResult,
6
+ StoreBanResult,
7
+ StoreBlockResult,
8
+ BanSnapshot,
9
+ BlockSnapshot,
10
+ } from './index';
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = require('../logger/console-reporter');
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = require('../rate-limit/limiter');
@@ -1,7 +0,0 @@
1
- 'use strict';
2
-
3
- const { Parry_DDoS, createParry } = require('./parry_ddos');
4
-
5
- // Keep the legacy public export name for compatibility while the project
6
- // prepares for a future package/API name that better reflects its scope.
7
- module.exports = { Parry_DDoS, createParry };
@@ -1,3 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = require('../express/middleware');
@@ -1,51 +0,0 @@
1
- # Store Contract
2
-
3
- Stores keep rate limit, temporary ban, and suspicious activity state for Parry.
4
- Methods may return values directly or return Promises.
5
-
6
- ```js
7
- store.incrementRateLimit(key, windowMs);
8
- // -> { key, count, resetAt, ttlMs }
9
-
10
- store.getRateLimit(key);
11
- // -> { key, count, resetAt, ttlMs }
12
-
13
- store.resetRateLimit(key);
14
- store.ban(key, ttlMs, metadata);
15
- store.isBanned(key);
16
- // -> { key, banned, banExpiresAt, metadata }
17
-
18
- store.unban(key);
19
- store.recordSuspicious(key, ttlMs, metadata);
20
- // -> { key, count, resetAt, ttlMs }
21
-
22
- store.incrementCounter(key, ttlMs, metadata);
23
- // -> { key, count, resetAt, ttlMs }
24
-
25
- store.getCounter(key);
26
- // -> { key, count, resetAt, ttlMs }
27
-
28
- store.resetCounter(key);
29
- store.blockKey(key, ttlMs, metadata);
30
- store.isBlocked(key);
31
- // -> { key, blocked, blockExpiresAt, metadata }
32
-
33
- store.unblockKey(key);
34
-
35
- store.listBans?.({ limit, offset });
36
- // -> [{ key, createdAt, banExpiresAt, ttlMs, metadata }]
37
-
38
- store.listBlocks?.({ limit, offset });
39
- // -> [{ key, createdAt, blockExpiresAt, ttlMs, metadata }]
40
-
41
- store.getStoreInfo?.();
42
- // -> { type, supportsAdminListing, ...metadata }
43
-
44
- store.close?.();
45
- ```
46
-
47
- `key` is produced by the rate limiter, route policy, or brute force key builder.
48
- Stores are responsible for TTL handling, cleanup, and any backend-specific
49
- namespacing. Custom stores should avoid persisting sensitive request data.
50
- Administrative listing methods are optional, but stores that implement them must
51
- return only sanitized metadata suitable for a read-only operations dashboard.