@arcis/node 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Arcis
2
2
 
3
- arcis One-line security middleware for Node.js, Python, and Go.
3
+ [![npm version](https://img.shields.io/npm/v/@arcis/node.svg)](https://www.npmjs.com/package/@arcis/node)
4
+ [![PyPI version](https://img.shields.io/pypi/v/arcis.svg)](https://pypi.org/project/arcis/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![CI](https://github.com/Gagancm/arcis/actions/workflows/ci.yml/badge.svg)](https://github.com/Gagancm/arcis/actions/workflows/ci.yml)
4
7
 
5
- Arcis protects your code like how Dependabot protects your dependencies.
8
+ One-line security middleware for Node.js, Python, and Go.
6
9
 
10
+ Arcis protects your code like how Dependabot protects your dependencies.
7
11
 
8
- **15 attack vectors handled so far.**
12
+ **15 attack vectors handled. 1040+ tests. Zero dependencies.**
9
13
 
10
14
  | Category | What it stops |
11
15
  |----------|--------------|
@@ -25,8 +29,6 @@ Arcis protects your code like how Dependabot protects your dependencies.
25
29
  | Security Headers | CSP, HSTS, X-Frame-Options, 10 headers out of the box |
26
30
  | Input Validation | Type checking, ranges, enums, mass assignment prevention, safe logging |
27
31
 
28
- **1040+ tests** across Node.js (613) and Python (430).
29
-
30
32
  ## Install
31
33
 
32
34
  ```bash
@@ -35,19 +37,6 @@ pip install arcis # Python
35
37
  go get github.com/GagancM/arcis # Go
36
38
  ```
37
39
 
38
- **Install directly from GitHub:**
39
-
40
- ```bash
41
- # Node.js
42
- npm install github:Gagancm/arcis#main --install-strategy=nested
43
-
44
- # Python
45
- pip install git+https://github.com/Gagancm/arcis.git#subdirectory=packages/arcis-python
46
-
47
- # Go
48
- go get github.com/Gagancm/arcis
49
- ```
50
-
51
40
  ## Quick Start
52
41
 
53
42
  ### Node.js
@@ -73,6 +73,103 @@ declare function createRateLimiter(options?: RateLimitOptions): RateLimiterMiddl
73
73
  */
74
74
  declare const rateLimit: typeof createRateLimiter;
75
75
 
76
+ /**
77
+ * @module @arcis/node/middleware/rate-limit-sliding
78
+ * Sliding window rate limiting middleware.
79
+ *
80
+ * More accurate than fixed window — uses a weighted sum of the previous
81
+ * and current window to approximate a true sliding window.
82
+ *
83
+ * Algorithm:
84
+ * weight = (windowMs - elapsed) / windowMs
85
+ * count = (prevWindow * weight) + currentWindow
86
+ * allow = count < limit
87
+ *
88
+ * @example
89
+ * app.use(createSlidingWindowLimiter({ max: 100, window: '15m' }));
90
+ */
91
+
92
+ interface SlidingWindowOptions {
93
+ /** Maximum requests per window. Default: 100 */
94
+ max?: number;
95
+ /** Window size in ms or duration string. Default: '1m' */
96
+ window?: string | number;
97
+ /** Error message when limit exceeded */
98
+ message?: string;
99
+ /** HTTP status code for rate limited responses. Default: 429 */
100
+ statusCode?: number;
101
+ /** Function to generate rate limit key from request */
102
+ keyGenerator?: (req: Request) => string;
103
+ /** Function to skip rate limiting for certain requests */
104
+ skip?: (req: Request) => boolean;
105
+ }
106
+ interface SlidingWindowMiddleware extends RequestHandler {
107
+ close: () => void;
108
+ }
109
+ /**
110
+ * Create sliding window rate limiter middleware.
111
+ *
112
+ * @example
113
+ * // 100 requests per 15 minutes
114
+ * app.use(createSlidingWindowLimiter({ max: 100, window: '15m' }));
115
+ *
116
+ * @example
117
+ * // Strict API limit
118
+ * app.use('/api', createSlidingWindowLimiter({ max: 30, window: '1m' }));
119
+ */
120
+ declare function createSlidingWindowLimiter(options?: SlidingWindowOptions): SlidingWindowMiddleware;
121
+
122
+ /**
123
+ * @module @arcis/node/middleware/rate-limit-token
124
+ * Token bucket rate limiting middleware.
125
+ *
126
+ * Allows burst traffic while enforcing an average rate.
127
+ * Tokens refill at a steady rate. Each request costs 1 token.
128
+ *
129
+ * Algorithm:
130
+ * tokens = min(capacity, tokens + elapsed * refillRate)
131
+ * if tokens >= cost: allow, subtract cost
132
+ * else: deny
133
+ *
134
+ * @example
135
+ * app.use(createTokenBucketLimiter({ capacity: 50, refillRate: 10 }));
136
+ */
137
+
138
+ interface TokenBucketOptions {
139
+ /** Maximum tokens (burst size). Default: 100 */
140
+ capacity?: number;
141
+ /** Tokens added per second. Default: 10 */
142
+ refillRate?: number;
143
+ /** Tokens consumed per request. Default: 1 */
144
+ cost?: number;
145
+ /** Error message when limit exceeded */
146
+ message?: string;
147
+ /** HTTP status code for rate limited responses. Default: 429 */
148
+ statusCode?: number;
149
+ /** Function to generate rate limit key from request */
150
+ keyGenerator?: (req: Request) => string;
151
+ /** Function to skip rate limiting for certain requests */
152
+ skip?: (req: Request) => boolean;
153
+ }
154
+ interface TokenBucketMiddleware extends RequestHandler {
155
+ close: () => void;
156
+ }
157
+ /**
158
+ * Create token bucket rate limiter middleware.
159
+ *
160
+ * @example
161
+ * // Allow bursts of 50, sustained rate of 10/sec
162
+ * app.use(createTokenBucketLimiter({ capacity: 50, refillRate: 10 }));
163
+ *
164
+ * @example
165
+ * // Strict API: 5 requests burst, 1/sec sustained
166
+ * app.use('/api/expensive', createTokenBucketLimiter({
167
+ * capacity: 5,
168
+ * refillRate: 1,
169
+ * }));
170
+ */
171
+ declare function createTokenBucketLimiter(options?: TokenBucketOptions): TokenBucketMiddleware;
172
+
76
173
  /**
77
174
  * @module @arcis/node/middleware/headers
78
175
  * Security headers middleware
@@ -252,4 +349,90 @@ declare function secureCookieDefaults(options?: SecureCookieOptions): RequestHan
252
349
  */
253
350
  declare const createSecureCookies: typeof secureCookieDefaults;
254
351
 
255
- export { type CorsOptions as C, type SecureCookieOptions as S, arcis as a, arcisWithMethods as b, createCors as c, createErrorHandler as d, createHeaders as e, createRateLimiter as f, createSecureCookies as g, enforceSecureCookie as h, errorHandler as i, secureCookieDefaults as j, securityHeaders as k, rateLimit as r, safeCors as s };
352
+ /**
353
+ * @module @arcis/node/middleware/bot-detection
354
+ * Local-only bot detection using User-Agent and behavioral signals.
355
+ *
356
+ * Categorizes requests into bot types and allows/denies based on config.
357
+ * No cloud calls — everything runs locally.
358
+ *
359
+ * @example
360
+ * // Block automated tools, allow search engines
361
+ * app.use(botProtection({
362
+ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'],
363
+ * deny: ['AUTOMATED', 'SCRAPER'],
364
+ * }));
365
+ */
366
+
367
+ type BotCategory = 'SEARCH_ENGINE' | 'SOCIAL' | 'MONITORING' | 'AI_CRAWLER' | 'SCRAPER' | 'AUTOMATED' | 'UNKNOWN' | 'HUMAN';
368
+ interface BotDetectionResult {
369
+ /** Whether the request appears to be from a bot */
370
+ isBot: boolean;
371
+ /** Bot category */
372
+ category: BotCategory;
373
+ /** Matched bot name (e.g. 'Googlebot', 'curl') or null */
374
+ name: string | null;
375
+ /** Confidence score: 0-1 */
376
+ confidence: number;
377
+ /** Behavioral signals detected */
378
+ signals: string[];
379
+ }
380
+ interface BotProtectionOptions {
381
+ /** Categories to explicitly allow. Default: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'] */
382
+ allow?: BotCategory[];
383
+ /** Categories to explicitly deny. Default: ['AUTOMATED'] */
384
+ deny?: BotCategory[];
385
+ /** Action for categories not in allow or deny. Default: 'allow' */
386
+ defaultAction?: 'allow' | 'deny';
387
+ /** HTTP status code for denied bots. Default: 403 */
388
+ statusCode?: number;
389
+ /** Error message for denied bots */
390
+ message?: string;
391
+ /** Enable behavioral signal detection. Default: true */
392
+ detectBehavior?: boolean;
393
+ /** Custom handler called on detection (instead of default deny response) */
394
+ onDetected?: (req: Request, res: Response, result: BotDetectionResult) => void;
395
+ }
396
+ /**
397
+ * Detect what kind of bot (if any) is making the request.
398
+ *
399
+ * @param req - HTTP request object
400
+ * @returns Detection result with category, name, confidence, and signals
401
+ *
402
+ * @example
403
+ * const result = detectBot(req);
404
+ * if (result.isBot && result.category === 'AUTOMATED') {
405
+ * // Block automated tools
406
+ * }
407
+ */
408
+ declare function detectBot(req: Request): BotDetectionResult;
409
+ /**
410
+ * Create Express middleware for bot protection.
411
+ *
412
+ * @example
413
+ * // Block automated tools and scrapers
414
+ * app.use(botProtection({
415
+ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'],
416
+ * deny: ['AUTOMATED', 'SCRAPER'],
417
+ * }));
418
+ *
419
+ * @example
420
+ * // Block everything except search engines
421
+ * app.use(botProtection({
422
+ * allow: ['SEARCH_ENGINE'],
423
+ * defaultAction: 'deny',
424
+ * }));
425
+ *
426
+ * @example
427
+ * // Custom handler
428
+ * app.use(botProtection({
429
+ * deny: ['AUTOMATED'],
430
+ * onDetected: (req, res, result) => {
431
+ * console.log(`Bot blocked: ${result.name} (${result.category})`);
432
+ * res.status(403).json({ error: 'Bots not allowed' });
433
+ * },
434
+ * }));
435
+ */
436
+ declare function botProtection(options?: BotProtectionOptions): RequestHandler;
437
+
438
+ export { type BotCategory as B, type CorsOptions as C, type SecureCookieOptions as S, type TokenBucketMiddleware as T, type BotDetectionResult as a, type BotProtectionOptions as b, type SlidingWindowMiddleware as c, type SlidingWindowOptions as d, type TokenBucketOptions as e, arcis as f, arcisWithMethods as g, botProtection as h, createCors as i, createErrorHandler as j, createHeaders as k, createRateLimiter as l, createSecureCookies as m, createSlidingWindowLimiter as n, createTokenBucketLimiter as o, detectBot as p, enforceSecureCookie as q, errorHandler as r, rateLimit as s, safeCors as t, secureCookieDefaults as u, securityHeaders as v };
@@ -73,6 +73,103 @@ declare function createRateLimiter(options?: RateLimitOptions): RateLimiterMiddl
73
73
  */
74
74
  declare const rateLimit: typeof createRateLimiter;
75
75
 
76
+ /**
77
+ * @module @arcis/node/middleware/rate-limit-sliding
78
+ * Sliding window rate limiting middleware.
79
+ *
80
+ * More accurate than fixed window — uses a weighted sum of the previous
81
+ * and current window to approximate a true sliding window.
82
+ *
83
+ * Algorithm:
84
+ * weight = (windowMs - elapsed) / windowMs
85
+ * count = (prevWindow * weight) + currentWindow
86
+ * allow = count < limit
87
+ *
88
+ * @example
89
+ * app.use(createSlidingWindowLimiter({ max: 100, window: '15m' }));
90
+ */
91
+
92
+ interface SlidingWindowOptions {
93
+ /** Maximum requests per window. Default: 100 */
94
+ max?: number;
95
+ /** Window size in ms or duration string. Default: '1m' */
96
+ window?: string | number;
97
+ /** Error message when limit exceeded */
98
+ message?: string;
99
+ /** HTTP status code for rate limited responses. Default: 429 */
100
+ statusCode?: number;
101
+ /** Function to generate rate limit key from request */
102
+ keyGenerator?: (req: Request) => string;
103
+ /** Function to skip rate limiting for certain requests */
104
+ skip?: (req: Request) => boolean;
105
+ }
106
+ interface SlidingWindowMiddleware extends RequestHandler {
107
+ close: () => void;
108
+ }
109
+ /**
110
+ * Create sliding window rate limiter middleware.
111
+ *
112
+ * @example
113
+ * // 100 requests per 15 minutes
114
+ * app.use(createSlidingWindowLimiter({ max: 100, window: '15m' }));
115
+ *
116
+ * @example
117
+ * // Strict API limit
118
+ * app.use('/api', createSlidingWindowLimiter({ max: 30, window: '1m' }));
119
+ */
120
+ declare function createSlidingWindowLimiter(options?: SlidingWindowOptions): SlidingWindowMiddleware;
121
+
122
+ /**
123
+ * @module @arcis/node/middleware/rate-limit-token
124
+ * Token bucket rate limiting middleware.
125
+ *
126
+ * Allows burst traffic while enforcing an average rate.
127
+ * Tokens refill at a steady rate. Each request costs 1 token.
128
+ *
129
+ * Algorithm:
130
+ * tokens = min(capacity, tokens + elapsed * refillRate)
131
+ * if tokens >= cost: allow, subtract cost
132
+ * else: deny
133
+ *
134
+ * @example
135
+ * app.use(createTokenBucketLimiter({ capacity: 50, refillRate: 10 }));
136
+ */
137
+
138
+ interface TokenBucketOptions {
139
+ /** Maximum tokens (burst size). Default: 100 */
140
+ capacity?: number;
141
+ /** Tokens added per second. Default: 10 */
142
+ refillRate?: number;
143
+ /** Tokens consumed per request. Default: 1 */
144
+ cost?: number;
145
+ /** Error message when limit exceeded */
146
+ message?: string;
147
+ /** HTTP status code for rate limited responses. Default: 429 */
148
+ statusCode?: number;
149
+ /** Function to generate rate limit key from request */
150
+ keyGenerator?: (req: Request) => string;
151
+ /** Function to skip rate limiting for certain requests */
152
+ skip?: (req: Request) => boolean;
153
+ }
154
+ interface TokenBucketMiddleware extends RequestHandler {
155
+ close: () => void;
156
+ }
157
+ /**
158
+ * Create token bucket rate limiter middleware.
159
+ *
160
+ * @example
161
+ * // Allow bursts of 50, sustained rate of 10/sec
162
+ * app.use(createTokenBucketLimiter({ capacity: 50, refillRate: 10 }));
163
+ *
164
+ * @example
165
+ * // Strict API: 5 requests burst, 1/sec sustained
166
+ * app.use('/api/expensive', createTokenBucketLimiter({
167
+ * capacity: 5,
168
+ * refillRate: 1,
169
+ * }));
170
+ */
171
+ declare function createTokenBucketLimiter(options?: TokenBucketOptions): TokenBucketMiddleware;
172
+
76
173
  /**
77
174
  * @module @arcis/node/middleware/headers
78
175
  * Security headers middleware
@@ -252,4 +349,90 @@ declare function secureCookieDefaults(options?: SecureCookieOptions): RequestHan
252
349
  */
253
350
  declare const createSecureCookies: typeof secureCookieDefaults;
254
351
 
255
- export { type CorsOptions as C, type SecureCookieOptions as S, arcis as a, arcisWithMethods as b, createCors as c, createErrorHandler as d, createHeaders as e, createRateLimiter as f, createSecureCookies as g, enforceSecureCookie as h, errorHandler as i, secureCookieDefaults as j, securityHeaders as k, rateLimit as r, safeCors as s };
352
+ /**
353
+ * @module @arcis/node/middleware/bot-detection
354
+ * Local-only bot detection using User-Agent and behavioral signals.
355
+ *
356
+ * Categorizes requests into bot types and allows/denies based on config.
357
+ * No cloud calls — everything runs locally.
358
+ *
359
+ * @example
360
+ * // Block automated tools, allow search engines
361
+ * app.use(botProtection({
362
+ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'],
363
+ * deny: ['AUTOMATED', 'SCRAPER'],
364
+ * }));
365
+ */
366
+
367
+ type BotCategory = 'SEARCH_ENGINE' | 'SOCIAL' | 'MONITORING' | 'AI_CRAWLER' | 'SCRAPER' | 'AUTOMATED' | 'UNKNOWN' | 'HUMAN';
368
+ interface BotDetectionResult {
369
+ /** Whether the request appears to be from a bot */
370
+ isBot: boolean;
371
+ /** Bot category */
372
+ category: BotCategory;
373
+ /** Matched bot name (e.g. 'Googlebot', 'curl') or null */
374
+ name: string | null;
375
+ /** Confidence score: 0-1 */
376
+ confidence: number;
377
+ /** Behavioral signals detected */
378
+ signals: string[];
379
+ }
380
+ interface BotProtectionOptions {
381
+ /** Categories to explicitly allow. Default: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'] */
382
+ allow?: BotCategory[];
383
+ /** Categories to explicitly deny. Default: ['AUTOMATED'] */
384
+ deny?: BotCategory[];
385
+ /** Action for categories not in allow or deny. Default: 'allow' */
386
+ defaultAction?: 'allow' | 'deny';
387
+ /** HTTP status code for denied bots. Default: 403 */
388
+ statusCode?: number;
389
+ /** Error message for denied bots */
390
+ message?: string;
391
+ /** Enable behavioral signal detection. Default: true */
392
+ detectBehavior?: boolean;
393
+ /** Custom handler called on detection (instead of default deny response) */
394
+ onDetected?: (req: Request, res: Response, result: BotDetectionResult) => void;
395
+ }
396
+ /**
397
+ * Detect what kind of bot (if any) is making the request.
398
+ *
399
+ * @param req - HTTP request object
400
+ * @returns Detection result with category, name, confidence, and signals
401
+ *
402
+ * @example
403
+ * const result = detectBot(req);
404
+ * if (result.isBot && result.category === 'AUTOMATED') {
405
+ * // Block automated tools
406
+ * }
407
+ */
408
+ declare function detectBot(req: Request): BotDetectionResult;
409
+ /**
410
+ * Create Express middleware for bot protection.
411
+ *
412
+ * @example
413
+ * // Block automated tools and scrapers
414
+ * app.use(botProtection({
415
+ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'],
416
+ * deny: ['AUTOMATED', 'SCRAPER'],
417
+ * }));
418
+ *
419
+ * @example
420
+ * // Block everything except search engines
421
+ * app.use(botProtection({
422
+ * allow: ['SEARCH_ENGINE'],
423
+ * defaultAction: 'deny',
424
+ * }));
425
+ *
426
+ * @example
427
+ * // Custom handler
428
+ * app.use(botProtection({
429
+ * deny: ['AUTOMATED'],
430
+ * onDetected: (req, res, result) => {
431
+ * console.log(`Bot blocked: ${result.name} (${result.category})`);
432
+ * res.status(403).json({ error: 'Bots not allowed' });
433
+ * },
434
+ * }));
435
+ */
436
+ declare function botProtection(options?: BotProtectionOptions): RequestHandler;
437
+
438
+ export { type BotCategory as B, type CorsOptions as C, type SecureCookieOptions as S, type TokenBucketMiddleware as T, type BotDetectionResult as a, type BotProtectionOptions as b, type SlidingWindowMiddleware as c, type SlidingWindowOptions as d, type TokenBucketOptions as e, arcis as f, arcisWithMethods as g, botProtection as h, createCors as i, createErrorHandler as j, createHeaders as k, createRateLimiter as l, createSecureCookies as m, createSlidingWindowLimiter as n, createTokenBucketLimiter as o, detectBot as p, enforceSecureCookie as q, errorHandler as r, rateLimit as s, safeCors as t, secureCookieDefaults as u, securityHeaders as v };