@od-oneapp/security 2026.2.1501-canary.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/README.md ADDED
@@ -0,0 +1,807 @@
1
+ # @repo/security
2
+
3
+ Comprehensive security infrastructure for Next.js and Node.js applications, providing rate limiting, bot detection, and
4
+ security headers.
5
+
6
+ ## Package Information
7
+
8
+ - **Can build:** NO (library package)
9
+ - **Framework:** Next.js 16+ (optional), Node.js 22+
10
+ - **Dependencies:** Arcjet, Nosecone, Upstash Redis
11
+
12
+ ## Exports
13
+
14
+ The package uses conditional exports for framework-specific implementations:
15
+
16
+ ```typescript
17
+ // Next.js Server-Side
18
+ import { secure, createRateLimiter } from "@repo/security/server/next";
19
+
20
+ // Generic Server-Side (Node.js, workers, etc.)
21
+ import { createRateLimiter } from "@repo/security/server";
22
+
23
+ // Next.js Client-Side (limited functionality)
24
+ import { getSecurityHeaders } from "@repo/security/client/next";
25
+
26
+ // Middleware (Next.js)
27
+ import { noseconeMiddleware, noseconeOptions } from "@repo/security/middleware";
28
+
29
+ // Environment utilities
30
+ import { env, safeEnv } from "@repo/security/keys";
31
+ ```
32
+
33
+ ## Core Features
34
+
35
+ ### 🤖 Bot Detection (Arcjet)
36
+
37
+ AI-powered bot detection with customizable allow/deny lists, Shield protection, and decision logging.
38
+
39
+ ### 🛡️ Security Headers (Nosecone)
40
+
41
+ Production-ready security headers including CSP, HSTS, X-Frame-Options, and more.
42
+
43
+ ### ⏱️ Rate Limiting (Upstash)
44
+
45
+ Distributed rate limiting with multiple strategies:
46
+
47
+ - **Sliding Window**: Smooth rate limiting over rolling time windows
48
+ - **Fixed Window**: Traditional rate limiting per fixed time period
49
+ - **Token Bucket**: Burst handling with token refill
50
+
51
+ ### 🌍 Environment Aware
52
+
53
+ Graceful degradation in development with production validation:
54
+
55
+ - Development: Logs warnings, allows requests without configuration
56
+ - Production: Fails closed, requires proper configuration
57
+
58
+ ## Quick Start
59
+
60
+ ### 1. Environment Setup
61
+
62
+ Create a `.env` file with the following variables:
63
+
64
+ ```bash
65
+ # Development (optional - graceful degradation without these)
66
+ ARCJET_KEY=ajkey_test_xxxxxxxxxxxxx
67
+ UPSTASH_REDIS_REST_URL=https://xxxxx.upstash.io
68
+ UPSTASH_REDIS_REST_TOKEN=xxxxxxxxxx
69
+
70
+ # Production (required for full functionality)
71
+ ARCJET_KEY=ajkey_xxxxxxxxxxxxx
72
+ UPSTASH_REDIS_REST_URL=https://xxxxx.upstash.io
73
+ UPSTASH_REDIS_REST_TOKEN=xxxxxxxxxx
74
+ ```
75
+
76
+ **Getting API Keys:**
77
+
78
+ - Arcjet: https://app.arcjet.com
79
+ - Upstash Redis: https://console.upstash.com
80
+
81
+ ### 2. Middleware Configuration (Next.js)
82
+
83
+ Add security headers middleware to your Next.js application:
84
+
85
+ ```typescript
86
+ // middleware.ts
87
+ import { noseconeMiddleware, noseconeOptions } from "@repo/security/middleware";
88
+
89
+ export const config = {
90
+ matcher: [
91
+ /*
92
+ * Match all request paths except:
93
+ * - _next/static (static files)
94
+ * - _next/image (image optimization files)
95
+ * - favicon.ico (favicon file)
96
+ */
97
+ "/((?!_next/static|_next/image|favicon.ico).*)"
98
+ ]
99
+ };
100
+
101
+ export default noseconeMiddleware(noseconeOptions);
102
+ ```
103
+
104
+ ### 3. API Route Protection
105
+
106
+ Protect API routes with bot detection and rate limiting:
107
+
108
+ ```typescript
109
+ // app/api/protected/route.ts
110
+ import { secure, createRateLimiter } from "@repo/security/server/next";
111
+ import { Ratelimit } from "@upstash/ratelimit";
112
+
113
+ const limiter = createRateLimiter({
114
+ limiter: Ratelimit.slidingWindow(10, "1 m"),
115
+ prefix: "api:protected"
116
+ });
117
+
118
+ export async function POST(request: Request) {
119
+ // 1. Bot Detection - Allow only Google and Bing bots
120
+ await secure(["GOOGLEBOT", "BINGBOT"], request);
121
+
122
+ // 2. Rate Limiting - 10 requests per minute
123
+ const ip = request.headers.get("x-forwarded-for") ?? "unknown";
124
+ const result = await limiter.limit(`api:protected:${ip}`);
125
+
126
+ if (!result.success) {
127
+ return new Response("Too many requests", {
128
+ status: 429,
129
+ headers: {
130
+ "Retry-After": String(Math.ceil((result.reset - Date.now()) / 1000))
131
+ }
132
+ });
133
+ }
134
+
135
+ // 3. Process request
136
+ return Response.json({ success: true });
137
+ }
138
+ ```
139
+
140
+ ## Detailed Usage
141
+
142
+ ### Bot Detection
143
+
144
+ The `secure()` function provides Arcjet-powered bot detection with Shield protection.
145
+
146
+ #### Allow Specific Bots
147
+
148
+ ```typescript
149
+ import { secure } from "@repo/security/server/next";
150
+
151
+ // Allow search engine crawlers
152
+ await secure(["GOOGLEBOT", "BINGBOT", "DUCKDUCKBOT"], request);
153
+
154
+ // Allow social media preview bots
155
+ await secure(["FACEBOOKBOT", "TWITTERBOT", "LINKEDINBOT"], request);
156
+
157
+ // Allow monitoring tools
158
+ await secure(["MONITOR"], request); // Category: monitoring tools
159
+
160
+ // Allow AI crawlers
161
+ await secure(["AI"], request); // Category: AI scrapers
162
+ ```
163
+
164
+ #### Block All Bots
165
+
166
+ ```typescript
167
+ // Block all automated traffic
168
+ await secure([], request);
169
+ ```
170
+
171
+ #### Available Bot Categories
172
+
173
+ **Individual Bots:**
174
+
175
+ - `GOOGLEBOT` - Google search crawler
176
+ - `BINGBOT` - Bing search crawler
177
+ - `DUCKDUCKBOT` - DuckDuckGo crawler
178
+ - `FACEBOOKBOT` - Facebook link preview
179
+ - `TWITTERBOT` - Twitter card preview
180
+ - `LINKEDINBOT` - LinkedIn preview
181
+ - `SLACKBOT` - Slack link preview
182
+ - `DISCORDBOT` - Discord embed preview
183
+
184
+ **Categories:**
185
+
186
+ - `CRAWLER` - All web crawlers
187
+ - `PREVIEW` - Social media preview bots
188
+ - `MONITOR` - Monitoring and uptime tools
189
+ - `AI` - AI training scrapers
190
+ - `SEO` - SEO analysis tools
191
+ - `ARCHIVE` - Web archiving services
192
+
193
+ #### Error Handling
194
+
195
+ ```typescript
196
+ import { secure, SecurityDenialError } from "@repo/security/server/next";
197
+
198
+ try {
199
+ await secure(["GOOGLEBOT"], request);
200
+ // Request is allowed
201
+ } catch (error) {
202
+ if (error instanceof SecurityDenialError) {
203
+ console.error("Security denial:", {
204
+ reason: error.reason, // 'bot' | 'rate_limit' | 'shield' | 'unknown'
205
+ ip: error.metadata?.ip,
206
+ path: error.metadata?.path,
207
+ userAgent: error.metadata?.userAgent,
208
+ decisionId: error.metadata?.decisionId
209
+ });
210
+
211
+ return new Response("Access denied", { status: 403 });
212
+ }
213
+ throw error;
214
+ }
215
+ ```
216
+
217
+ ### Rate Limiting
218
+
219
+ #### Basic Usage
220
+
221
+ ```typescript
222
+ import { applyRateLimit } from "@repo/security/server/next";
223
+
224
+ // Apply rate limit using pre-configured limiter
225
+ const ip = request.headers.get("x-forwarded-for") ?? "unknown";
226
+ const result = await applyRateLimit(ip, "api");
227
+
228
+ if (!result.success) {
229
+ return new Response("Too many requests", {
230
+ status: 429,
231
+ headers: {
232
+ "X-RateLimit-Limit": String(result.limit),
233
+ "X-RateLimit-Remaining": String(result.remaining),
234
+ "X-RateLimit-Reset": String(result.reset),
235
+ "Retry-After": String(Math.ceil((result.reset - Date.now()) / 1000))
236
+ }
237
+ });
238
+ }
239
+ ```
240
+
241
+ #### Pre-configured Rate Limiters
242
+
243
+ The package provides pre-configured rate limiters for common use cases:
244
+
245
+ ```typescript
246
+ import { rateLimiters } from "@repo/security/server/next";
247
+
248
+ // API endpoints - 100 requests per minute
249
+ const apiResult = await rateLimiters.api.limit(identifier);
250
+
251
+ // Authentication - 5 attempts per 15 minutes
252
+ const authResult = await rateLimiters.auth.limit(userId);
253
+
254
+ // File uploads - 10 uploads per hour
255
+ const uploadResult = await rateLimiters.upload.limit(userId);
256
+
257
+ // Webhooks - 1000 requests per hour
258
+ const webhookResult = await rateLimiters.webhook.limit(webhookId);
259
+
260
+ // Search - 500 requests per minute
261
+ const searchResult = await rateLimiters.search.limit(ip);
262
+ ```
263
+
264
+ #### Custom Rate Limiters
265
+
266
+ Create custom rate limiters with different strategies:
267
+
268
+ ```typescript
269
+ import { createRateLimiter, slidingWindow, fixedWindow, tokenBucket } from "@repo/security/server/next";
270
+
271
+ // Sliding window - 50 requests per 10 seconds
272
+ const customLimiter = createRateLimiter({
273
+ limiter: slidingWindow(50, "10 s"),
274
+ prefix: "custom:api"
275
+ });
276
+
277
+ // Fixed window - 1000 requests per hour
278
+ const hourlyLimiter = createRateLimiter({
279
+ limiter: fixedWindow(1000, "1 h"),
280
+ prefix: "hourly"
281
+ });
282
+
283
+ // Token bucket - 100 requests with 10 per second refill
284
+ const burstLimiter = createRateLimiter({
285
+ limiter: tokenBucket(100, "10 s"),
286
+ prefix: "burst"
287
+ });
288
+ ```
289
+
290
+ #### Rate Limit Utilities
291
+
292
+ ```typescript
293
+ import { applyRateLimit, isRateLimited, getRateLimitInfo } from "@repo/security/server/next";
294
+
295
+ // Check if request should be blocked
296
+ const isBlocked = await isRateLimited(identifier, "api");
297
+ if (isBlocked) {
298
+ return new Response("Rate limited", { status: 429 });
299
+ }
300
+
301
+ // Get rate limit info without applying limits
302
+ const info = await getRateLimitInfo(identifier, "api");
303
+ console.log(`Remaining: ${info.remaining}/${info.limit}`);
304
+
305
+ // Apply rate limit and get full result
306
+ const result = await applyRateLimit(identifier, "api");
307
+ console.log(result);
308
+ // {
309
+ // success: true,
310
+ // limit: 100,
311
+ // remaining: 99,
312
+ // reset: 1234567890,
313
+ // retryAfter: undefined
314
+ // }
315
+ ```
316
+
317
+ ### Security Headers
318
+
319
+ Configure security headers via Nosecone middleware:
320
+
321
+ ```typescript
322
+ // middleware.ts
323
+ import { noseconeMiddleware } from "@repo/security/middleware";
324
+ import type { NoseconeOptions } from "@nosecone/next";
325
+
326
+ // Custom configuration
327
+ const customOptions: NoseconeOptions = {
328
+ // Content Security Policy
329
+ contentSecurityPolicy: {
330
+ directives: {
331
+ defaultSrc: ["'self'"],
332
+ scriptSrc: ["'self'", "'unsafe-inline'"],
333
+ styleSrc: ["'self'", "'unsafe-inline'"],
334
+ imgSrc: ["'self'", "data:", "https:"],
335
+ fontSrc: ["'self'", "data:"],
336
+ connectSrc: ["'self'"],
337
+ frameSrc: ["'none'"]
338
+ }
339
+ },
340
+
341
+ // Strict Transport Security
342
+ strictTransportSecurity: {
343
+ maxAge: 31536000,
344
+ includeSubDomains: true,
345
+ preload: true
346
+ },
347
+
348
+ // X-Frame-Options
349
+ xFrameOptions: "DENY",
350
+
351
+ // X-Content-Type-Options
352
+ xContentTypeOptions: "nosniff",
353
+
354
+ // Referrer Policy
355
+ referrerPolicy: "strict-origin-when-cross-origin",
356
+
357
+ // Permissions Policy
358
+ permissionsPolicy: {
359
+ camera: ["none"],
360
+ microphone: ["none"],
361
+ geolocation: ["none"]
362
+ }
363
+ };
364
+
365
+ export default noseconeMiddleware(customOptions);
366
+ ```
367
+
368
+ ### Environment Management
369
+
370
+ The package uses `@t3-oss/env-core` for type-safe environment variable validation:
371
+
372
+ ```typescript
373
+ import { env, safeEnv, isProduction, hasArcjetConfig, hasUpstashConfig } from "@repo/security/keys";
374
+
375
+ // Access validated environment variables
376
+ console.log(env.ARCJET_KEY); // string | undefined
377
+ console.log(env.NODE_ENV); // 'development' | 'test' | 'production'
378
+
379
+ // Safe access (always returns object, no exceptions)
380
+ const config = safeEnv();
381
+ console.log(config.UPSTASH_REDIS_REST_URL);
382
+
383
+ // Helper functions
384
+ if (isProduction()) {
385
+ // Production-only logic
386
+ }
387
+
388
+ if (hasArcjetConfig()) {
389
+ // Arcjet is configured
390
+ }
391
+
392
+ if (hasUpstashConfig()) {
393
+ // Redis is configured
394
+ }
395
+ ```
396
+
397
+ ### Custom Logger
398
+
399
+ Replace the default logger with your own implementation:
400
+
401
+ ```typescript
402
+ import { setLogger } from "@repo/security/keys";
403
+ import pino from "pino";
404
+
405
+ const logger = pino({
406
+ level: "info",
407
+ transport: {
408
+ target: "pino-pretty"
409
+ }
410
+ });
411
+
412
+ setLogger({
413
+ warn: (message, context) => logger.warn(context, message),
414
+ error: (message, context) => logger.error(context, message)
415
+ });
416
+ ```
417
+
418
+ ## Architecture
419
+
420
+ ### Module Organization
421
+
422
+ ```
423
+ @repo/security/
424
+ ├── src/
425
+ │ ├── server.ts # Generic server exports
426
+ │ ├── server-next.ts # Next.js server exports (Arcjet, rate limiting)
427
+ │ ├── client.ts # Generic client exports
428
+ │ └── client-next.ts # Next.js client exports
429
+ ├── env.ts # Environment variable validation
430
+ ├── rate-limit.ts # Rate limiting logic (Upstash)
431
+ ├── middleware.ts # Security headers middleware (Nosecone)
432
+ └── __tests__/ # Test suites
433
+ ```
434
+
435
+ ### Dependency Graph
436
+
437
+ ```
438
+ src/server-next.ts
439
+ ├─> env.ts (environment validation)
440
+ ├─> rate-limit.ts (rate limiting)
441
+ │ └─> env.ts
442
+ │ └─> @repo/db-upstash-redis/server
443
+ └─> middleware.ts (security headers)
444
+ └─> @nosecone/next
445
+ ```
446
+
447
+ ### Design Principles
448
+
449
+ 1. **Graceful Degradation**: Works without configuration in development
450
+ 2. **Fail Closed**: Denies requests on errors in production
451
+ 3. **Framework Agnostic**: Core logic works with any framework
452
+ 4. **Type Safe**: Full TypeScript support with branded types
453
+ 5. **Observable**: Structured logging for monitoring
454
+
455
+ ## Edge Cases & Failure Scenarios
456
+
457
+ ### Missing Environment Variables
458
+
459
+ **Development:**
460
+
461
+ - Rate limiting: Returns no-op limiter, logs warning
462
+ - Bot detection: Skips validation, allows all requests
463
+
464
+ **Production:**
465
+
466
+ - Rate limiting: Throws error on limiter creation
467
+ - Bot detection: Throws error on validation failure
468
+
469
+ ### Redis Connection Failures
470
+
471
+ **Development:**
472
+
473
+ - Falls back to allowing requests
474
+ - Logs error with context
475
+
476
+ **Production:**
477
+
478
+ - Denies requests (fail closed)
479
+ - Returns 500 error
480
+
481
+ ### Arcjet API Failures
482
+
483
+ **Development:**
484
+
485
+ - Allows requests after logging error
486
+ - Useful for local development
487
+
488
+ **Production:**
489
+
490
+ - Denies requests (fail closed)
491
+ - Returns 403 error
492
+
493
+ ### Rate Limit Identifier Validation
494
+
495
+ ```typescript
496
+ // Valid identifiers (alphanumeric, hyphens, underscores, colons, dots)
497
+ await applyRateLimit("user-123", "api"); // ✓
498
+ await applyRateLimit("192.168.1.1", "api"); // ✓
499
+ await applyRateLimit("api:v1:users", "api"); // ✓
500
+
501
+ // Invalid identifiers (contains special characters)
502
+ await applyRateLimit("user@example.com", "api"); // ✗ Throws error
503
+ await applyRateLimit("user/123", "api"); // ✗ Throws error
504
+
505
+ // Too long (>255 characters)
506
+ await applyRateLimit("a".repeat(256), "api"); // ✗ Throws error
507
+ ```
508
+
509
+ ## Common Patterns
510
+
511
+ ### Authenticated vs Anonymous Rate Limiting
512
+
513
+ ```typescript
514
+ import { applyRateLimit } from "@repo/security/server/next";
515
+
516
+ export async function POST(request: Request) {
517
+ const session = await getSession(request);
518
+
519
+ // Different rate limits for authenticated vs anonymous
520
+ const identifier = session?.userId
521
+ ? `user:${session.userId}`
522
+ : `ip:${request.headers.get("x-forwarded-for") ?? "unknown"}`;
523
+
524
+ const limiterType = session?.userId ? "api" : "auth";
525
+
526
+ const result = await applyRateLimit(identifier, limiterType);
527
+
528
+ if (!result.success) {
529
+ return new Response("Rate limit exceeded", { status: 429 });
530
+ }
531
+
532
+ // Process request
533
+ }
534
+ ```
535
+
536
+ ### Progressive Rate Limiting
537
+
538
+ ```typescript
539
+ import { createRateLimiter, slidingWindow } from "@repo/security/server/next";
540
+
541
+ // Tier 1: 10 requests per minute (free tier)
542
+ const freeLimiter = createRateLimiter({
543
+ limiter: slidingWindow(10, "1 m"),
544
+ prefix: "free"
545
+ });
546
+
547
+ // Tier 2: 100 requests per minute (pro tier)
548
+ const proLimiter = createRateLimiter({
549
+ limiter: slidingWindow(100, "1 m"),
550
+ prefix: "pro"
551
+ });
552
+
553
+ // Tier 3: 1000 requests per minute (enterprise)
554
+ const enterpriseLimiter = createRateLimiter({
555
+ limiter: slidingWindow(1000, "1 m"),
556
+ prefix: "enterprise"
557
+ });
558
+
559
+ export async function GET(request: Request) {
560
+ const user = await getUser(request);
561
+ const limiter = user.tier === "enterprise" ? enterpriseLimiter : user.tier === "pro" ? proLimiter : freeLimiter;
562
+
563
+ const result = await limiter.limit(`user:${user.id}`);
564
+ // ... handle result
565
+ }
566
+ ```
567
+
568
+ ### Combined Protection
569
+
570
+ ```typescript
571
+ import { secure, applyRateLimit, SecurityDenialError } from "@repo/security/server/next";
572
+
573
+ export async function POST(request: Request) {
574
+ try {
575
+ // 1. Shield Protection (Arcjet)
576
+ await secure([], request); // Block all bots
577
+
578
+ // 2. Rate Limiting
579
+ const ip = request.headers.get("x-forwarded-for") ?? "unknown";
580
+ const result = await applyRateLimit(ip, "api");
581
+
582
+ if (!result.success) {
583
+ return new Response("Rate limit exceeded", {
584
+ status: 429,
585
+ headers: {
586
+ "Retry-After": String(Math.ceil((result.reset - Date.now()) / 1000))
587
+ }
588
+ });
589
+ }
590
+
591
+ // 3. Process request
592
+ return Response.json({ success: true });
593
+ } catch (error) {
594
+ if (error instanceof SecurityDenialError) {
595
+ // Log security event
596
+ console.error("Security denial:", error.metadata);
597
+
598
+ return new Response("Access denied", {
599
+ status: 403,
600
+ headers: {
601
+ "X-Denial-Reason": error.reason
602
+ }
603
+ });
604
+ }
605
+ throw error;
606
+ }
607
+ }
608
+ ```
609
+
610
+ ### Rate Limit Headers
611
+
612
+ ```typescript
613
+ import { getRateLimitInfo } from "@repo/security/server/next";
614
+
615
+ export async function GET(request: Request) {
616
+ const ip = request.headers.get("x-forwarded-for") ?? "unknown";
617
+ const info = await getRateLimitInfo(ip, "api");
618
+
619
+ return new Response("OK", {
620
+ headers: {
621
+ "X-RateLimit-Limit": String(info.limit),
622
+ "X-RateLimit-Remaining": String(info.remaining),
623
+ "X-RateLimit-Reset": String(info.reset)
624
+ }
625
+ });
626
+ }
627
+ ```
628
+
629
+ ## Testing
630
+
631
+ ### Running Tests
632
+
633
+ ```bash
634
+ # Run all tests
635
+ pnpm test
636
+
637
+ # Run tests in watch mode
638
+ pnpm test:watch
639
+
640
+ # Run with coverage
641
+ pnpm test:coverage
642
+
643
+ # Generate coverage report
644
+ pnpm coverage:collect
645
+ ```
646
+
647
+ ### Test Structure
648
+
649
+ ```typescript
650
+ import { describe, it, expect, beforeEach, vi } from "vitest";
651
+ import { applyRateLimit } from "@repo/security/server/next";
652
+
653
+ describe("rate limiting", () => {
654
+ beforeEach(() => {
655
+ vi.resetModules();
656
+ });
657
+
658
+ it("should enforce rate limits", async () => {
659
+ const result = await applyRateLimit("test-user", "api");
660
+ expect(result.success).toBe(true);
661
+ });
662
+
663
+ it("should return retryAfter when limit exceeded", async () => {
664
+ // Exhaust rate limit
665
+ for (let i = 0; i < 100; i++) {
666
+ await applyRateLimit("test-user", "api");
667
+ }
668
+
669
+ const result = await applyRateLimit("test-user", "api");
670
+ expect(result.success).toBe(false);
671
+ expect(result.retryAfter).toBeGreaterThan(0);
672
+ });
673
+ });
674
+ ```
675
+
676
+ ## Troubleshooting
677
+
678
+ ### Rate Limiting Not Working
679
+
680
+ **Symptoms:** All requests are allowed, no rate limiting
681
+
682
+ **Causes:**
683
+
684
+ 1. Redis not configured (check `UPSTASH_REDIS_REST_TOKEN` and `UPSTASH_REDIS_REST_URL`)
685
+ 2. Running in development mode (graceful degradation)
686
+ 3. Identifier is different for each request (check IP extraction)
687
+
688
+ **Solution:**
689
+
690
+ ```typescript
691
+ import { hasUpstashConfig } from "@repo/security/keys";
692
+
693
+ if (!hasUpstashConfig()) {
694
+ console.error("Redis not configured - rate limiting disabled");
695
+ }
696
+ ```
697
+
698
+ ### Bot Detection Not Blocking
699
+
700
+ **Symptoms:** Bots are not being blocked
701
+
702
+ **Causes:**
703
+
704
+ 1. Arcjet not configured (check `ARCJET_KEY`)
705
+ 2. Bot is in allow list
706
+ 3. Running in development mode
707
+
708
+ **Solution:**
709
+
710
+ ```typescript
711
+ import { hasArcjetConfig } from "@repo/security/keys";
712
+
713
+ if (!hasArcjetConfig()) {
714
+ console.error("Arcjet not configured - bot detection disabled");
715
+ }
716
+
717
+ // Check bot allow list
718
+ await secure([], request); // Empty array = block all bots
719
+ ```
720
+
721
+ ### Memory Leaks
722
+
723
+ **Symptoms:** Server memory usage growing over time
724
+
725
+ **Causes:**
726
+
727
+ 1. Rate limit info cache not being cleaned up
728
+ 2. Too many unique identifiers
729
+
730
+ **Solution:**
731
+
732
+ - Monitor cache size
733
+ - Implement LRU cache with size limit
734
+ - Use consistent identifier format
735
+
736
+ ## Performance Considerations
737
+
738
+ ### Rate Limiting Overhead
739
+
740
+ - **Redis latency**: ~2-5ms per rate limit check
741
+ - **Cache hit rate**: ~60-80% for typical usage
742
+ - **Timeout**: 5 seconds (configurable)
743
+
744
+ ### Optimization Tips
745
+
746
+ 1. **Use cache**: `getRateLimitInfo()` caches for 1 second
747
+ 2. **Batch requests**: Check rate limit once per session
748
+ 3. **Use sliding window**: More efficient than token bucket
749
+ 4. **Pre-warm cache**: Prime cache on server start
750
+
751
+ ## Migration Guide
752
+
753
+ ### From v1 to v2
754
+
755
+ **Breaking Changes:**
756
+
757
+ 1. `secure()` now throws `SecurityDenialError` instead of returning boolean
758
+ 2. Rate limiters require Redis configuration in production
759
+ 3. Environment variables must follow new validation schema
760
+
761
+ **Migration Steps:**
762
+
763
+ ```typescript
764
+ // Before (v1)
765
+ const isAllowed = await secure(["GOOGLEBOT"], request);
766
+ if (!isAllowed) {
767
+ return new Response("Denied", { status: 403 });
768
+ }
769
+
770
+ // After (v2)
771
+ try {
772
+ await secure(["GOOGLEBOT"], request);
773
+ } catch (error) {
774
+ if (error instanceof SecurityDenialError) {
775
+ return new Response("Denied", { status: 403 });
776
+ }
777
+ throw error;
778
+ }
779
+ ```
780
+
781
+ ## Related Packages
782
+
783
+ - `@repo/auth` - Authentication package (uses `@repo/security` for rate limiting)
784
+ - `@repo/db-upstash-redis` - Shared Redis client
785
+ - `@arcjet/next` - Arcjet Next.js SDK
786
+ - `@nosecone/next` - Nosecone security headers
787
+ - `@upstash/ratelimit` - Upstash rate limiting SDK
788
+
789
+ ## Additional Resources
790
+
791
+ - [Arcjet Documentation](https://docs.arcjet.com)
792
+ - [Nosecone Documentation](https://docs.arcjet.com/nosecone/quick-start)
793
+ - [Upstash Rate Limiting](https://upstash.com/docs/oss/sdks/ts/ratelimit/overview)
794
+ - [Security Best Practices](../../apps/docs/packages/security.mdx)
795
+
796
+ ## License
797
+
798
+ Private package - not for external distribution.
799
+
800
+ ## 📚 Comprehensive Documentation
801
+
802
+ For detailed documentation, see:
803
+
804
+ - **[Audit Reports](../../apps/docs/content/docs/audits/security/)** - Comprehensive audits, fixes, and security reviews
805
+ - **[Technical Guides](../../apps/docs/content/docs/packages/security/)** - Implementation guides and best practices
806
+
807
+ All comprehensive documentation has been centralized in the docs app.