@stacksjs/bun-router 0.0.17 → 0.0.18
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/dist/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -733
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -448
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- package/src/websocket/clustering.ts +0 -769
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
import { config } from '../config'
|
|
3
|
-
|
|
4
|
-
export interface SecurityOptions {
|
|
5
|
-
// Input validation and sanitization
|
|
6
|
-
inputValidation?: {
|
|
7
|
-
enabled?: boolean
|
|
8
|
-
maxBodySize?: number
|
|
9
|
-
maxUrlLength?: number
|
|
10
|
-
maxHeaderSize?: number
|
|
11
|
-
allowedContentTypes?: string[]
|
|
12
|
-
sanitizeInput?: boolean
|
|
13
|
-
validateEncoding?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Request filtering
|
|
17
|
-
requestFiltering?: {
|
|
18
|
-
enabled?: boolean
|
|
19
|
-
blockSuspiciousPatterns?: boolean
|
|
20
|
-
maxRequestsPerMinute?: number
|
|
21
|
-
blockUserAgents?: string[]
|
|
22
|
-
allowedMethods?: string[]
|
|
23
|
-
requireUserAgent?: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// IP filtering and geoblocking
|
|
27
|
-
ipFiltering?: {
|
|
28
|
-
enabled?: boolean
|
|
29
|
-
whitelist?: string[]
|
|
30
|
-
blacklist?: string[]
|
|
31
|
-
blockPrivateIPs?: boolean
|
|
32
|
-
blockCloudProviders?: boolean
|
|
33
|
-
geoBlocking?: {
|
|
34
|
-
enabled?: boolean
|
|
35
|
-
allowedCountries?: string[]
|
|
36
|
-
blockedCountries?: string[]
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Attack prevention
|
|
41
|
-
attackPrevention?: {
|
|
42
|
-
enabled?: boolean
|
|
43
|
-
sqlInjection?: boolean
|
|
44
|
-
xss?: boolean
|
|
45
|
-
pathTraversal?: boolean
|
|
46
|
-
commandInjection?: boolean
|
|
47
|
-
ldapInjection?: boolean
|
|
48
|
-
xxe?: boolean
|
|
49
|
-
customPatterns?: Array<{
|
|
50
|
-
name: string
|
|
51
|
-
pattern: RegExp
|
|
52
|
-
action: 'block' | 'log' | 'sanitize'
|
|
53
|
-
}>
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Response security
|
|
57
|
-
responseSecurity?: {
|
|
58
|
-
enabled?: boolean
|
|
59
|
-
removeServerHeaders?: boolean
|
|
60
|
-
addSecurityHeaders?: boolean
|
|
61
|
-
sanitizeErrors?: boolean
|
|
62
|
-
preventInfoDisclosure?: boolean
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export default class Security {
|
|
67
|
-
private options: SecurityOptions
|
|
68
|
-
private suspiciousPatterns: RegExp[] = []
|
|
69
|
-
private sqlInjectionPatterns: RegExp[] = []
|
|
70
|
-
private xssPatterns: RegExp[] = []
|
|
71
|
-
private pathTraversalPatterns: RegExp[] = []
|
|
72
|
-
private commandInjectionPatterns: RegExp[] = []
|
|
73
|
-
|
|
74
|
-
constructor(options: SecurityOptions = {}) {
|
|
75
|
-
const _securityConfig = config.server?.security || {}
|
|
76
|
-
|
|
77
|
-
this.options = {
|
|
78
|
-
inputValidation: {
|
|
79
|
-
enabled: true,
|
|
80
|
-
maxBodySize: 10 * 1024 * 1024, // 10MB
|
|
81
|
-
maxUrlLength: 2048,
|
|
82
|
-
maxHeaderSize: 8192,
|
|
83
|
-
allowedContentTypes: [
|
|
84
|
-
'application/json',
|
|
85
|
-
'application/x-www-form-urlencoded',
|
|
86
|
-
'multipart/form-data',
|
|
87
|
-
'text/plain',
|
|
88
|
-
'text/html',
|
|
89
|
-
'application/xml',
|
|
90
|
-
'text/xml',
|
|
91
|
-
],
|
|
92
|
-
sanitizeInput: true,
|
|
93
|
-
validateEncoding: true,
|
|
94
|
-
...options.inputValidation,
|
|
95
|
-
},
|
|
96
|
-
requestFiltering: {
|
|
97
|
-
enabled: true,
|
|
98
|
-
blockSuspiciousPatterns: true,
|
|
99
|
-
maxRequestsPerMinute: 1000,
|
|
100
|
-
blockUserAgents: [
|
|
101
|
-
'sqlmap',
|
|
102
|
-
'nikto',
|
|
103
|
-
'nessus',
|
|
104
|
-
'openvas',
|
|
105
|
-
'nmap',
|
|
106
|
-
'masscan',
|
|
107
|
-
'zap',
|
|
108
|
-
'w3af',
|
|
109
|
-
],
|
|
110
|
-
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
|
|
111
|
-
requireUserAgent: false,
|
|
112
|
-
...options.requestFiltering,
|
|
113
|
-
},
|
|
114
|
-
ipFiltering: {
|
|
115
|
-
enabled: false,
|
|
116
|
-
whitelist: [],
|
|
117
|
-
blacklist: [],
|
|
118
|
-
blockPrivateIPs: false,
|
|
119
|
-
blockCloudProviders: false,
|
|
120
|
-
geoBlocking: {
|
|
121
|
-
enabled: false,
|
|
122
|
-
allowedCountries: [],
|
|
123
|
-
blockedCountries: [],
|
|
124
|
-
},
|
|
125
|
-
...options.ipFiltering,
|
|
126
|
-
},
|
|
127
|
-
attackPrevention: {
|
|
128
|
-
enabled: true,
|
|
129
|
-
sqlInjection: true,
|
|
130
|
-
xss: true,
|
|
131
|
-
pathTraversal: true,
|
|
132
|
-
commandInjection: true,
|
|
133
|
-
ldapInjection: true,
|
|
134
|
-
xxe: true,
|
|
135
|
-
customPatterns: [],
|
|
136
|
-
...options.attackPrevention,
|
|
137
|
-
},
|
|
138
|
-
responseSecurity: {
|
|
139
|
-
enabled: true,
|
|
140
|
-
removeServerHeaders: true,
|
|
141
|
-
addSecurityHeaders: true,
|
|
142
|
-
sanitizeErrors: true,
|
|
143
|
-
preventInfoDisclosure: true,
|
|
144
|
-
...options.responseSecurity,
|
|
145
|
-
},
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this.initializePatterns()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private initializePatterns(): void {
|
|
152
|
-
// Suspicious request patterns
|
|
153
|
-
this.suspiciousPatterns = [
|
|
154
|
-
/\.\.\//g, // Path traversal
|
|
155
|
-
/\.\.\\/g, // Windows path traversal
|
|
156
|
-
/%2e%2e%2f/gi, // URL encoded path traversal
|
|
157
|
-
/%2e%2e%5c/gi, // URL encoded Windows path traversal
|
|
158
|
-
/\/etc\/passwd/gi,
|
|
159
|
-
/\/proc\/self\/environ/gi,
|
|
160
|
-
/cmd\.exe/gi,
|
|
161
|
-
/powershell/gi,
|
|
162
|
-
/<script/gi,
|
|
163
|
-
/javascript:/gi,
|
|
164
|
-
/vbscript:/gi,
|
|
165
|
-
/onload=/gi,
|
|
166
|
-
/onerror=/gi,
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
// SQL injection patterns
|
|
170
|
-
this.sqlInjectionPatterns = [
|
|
171
|
-
/(\bunion\b.+\bselect\b)|(\bselect\b.+\bunion\b)/gi,
|
|
172
|
-
/\b(select|insert|update|delete|drop|create|alter|exec|execute)\b.+\b(from|into|set|where|table|database|schema)\b/gi,
|
|
173
|
-
// eslint-disable-next-line quotes -- regex pattern needs both quote types
|
|
174
|
-
/(\bor\b|\band\b).+['"]\s*=\s*['"]|['"]\s*=\s*['"].+(\bor\b|\band\b)/gi,
|
|
175
|
-
/\b(sleep|benchmark|waitfor)\s*\(/gi,
|
|
176
|
-
/\b(information_schema|sys\.databases|sysobjects)\b/gi,
|
|
177
|
-
/('|(\\')).+(or|and)\s+(?:\S.*|[\t\v\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF])[=><]/gi,
|
|
178
|
-
/\b(concat|group_concat|load_file|into\s+outfile)\b/gi,
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
// XSS patterns
|
|
182
|
-
this.xssPatterns = [
|
|
183
|
-
/<script[^>]*>.*?<\/script>/gi,
|
|
184
|
-
/<iframe[^>]*>.*?<\/iframe>/gi,
|
|
185
|
-
/<object[^>]*>.*?<\/object>/gi,
|
|
186
|
-
/<embed[^>]*>/gi,
|
|
187
|
-
/<link[^>]*>/gi,
|
|
188
|
-
/<meta[^>]*>/gi,
|
|
189
|
-
/javascript:/gi,
|
|
190
|
-
/vbscript:/gi,
|
|
191
|
-
/on\w+\s*=/gi,
|
|
192
|
-
/<[^>]*\son\w+\s*=.*?>/gi,
|
|
193
|
-
/expression\s*\(/gi,
|
|
194
|
-
/url\s*\(\s*javascript:/gi,
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
// Path traversal patterns
|
|
198
|
-
this.pathTraversalPatterns = [
|
|
199
|
-
/\.\.\//g,
|
|
200
|
-
/\.\.\\/g,
|
|
201
|
-
/%2e%2e%2f/gi,
|
|
202
|
-
/%2e%2e%5c/gi,
|
|
203
|
-
/\.\.%2f/gi,
|
|
204
|
-
/\.\.%5c/gi,
|
|
205
|
-
/%252e%252e%252f/gi,
|
|
206
|
-
/\.\/%2e%2e%2f/gi,
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
// Command injection patterns
|
|
210
|
-
this.commandInjectionPatterns = [
|
|
211
|
-
/[;&|`${}[\]]/g, // Removed parentheses to avoid false positives with user agents
|
|
212
|
-
/\b(cat|ls|pwd|id|whoami|uname|ps|netstat|ifconfig|ping|nslookup|dig|curl|wget)\s/gi,
|
|
213
|
-
/\b(cmd|powershell|bash|sh|zsh|fish)\s/gi,
|
|
214
|
-
/\b(exec|system|shell_exec|passthru|eval)\s*\(/gi,
|
|
215
|
-
/\$\{.*\}/g, // Template injection
|
|
216
|
-
/#\{.*\}/g, // Ruby template injection
|
|
217
|
-
]
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private getClientIP(req: EnhancedRequest): string {
|
|
221
|
-
return req.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
|
|
222
|
-
|| req.headers.get('x-real-ip')
|
|
223
|
-
|| req.headers.get('cf-connecting-ip')
|
|
224
|
-
|| req.headers.get('x-client-ip')
|
|
225
|
-
|| 'unknown'
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private isPrivateIP(ip: string): boolean {
|
|
229
|
-
const privateRanges = [
|
|
230
|
-
/^10\./,
|
|
231
|
-
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
232
|
-
/^192\.168\./,
|
|
233
|
-
/^127\./,
|
|
234
|
-
/^169\.254\./,
|
|
235
|
-
/^::1$/,
|
|
236
|
-
/^fc00:/,
|
|
237
|
-
/^fe80:/,
|
|
238
|
-
]
|
|
239
|
-
return privateRanges.some(range => range.test(ip))
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private validateInput(input: string): { isValid: boolean, threats: string[] } {
|
|
243
|
-
const threats: string[] = []
|
|
244
|
-
|
|
245
|
-
if (!this.options.attackPrevention?.enabled) {
|
|
246
|
-
return { isValid: true, threats }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Check for SQL injection
|
|
250
|
-
if (this.options.attackPrevention.sqlInjection) {
|
|
251
|
-
for (const pattern of this.sqlInjectionPatterns) {
|
|
252
|
-
if (pattern.test(input)) {
|
|
253
|
-
threats.push('SQL Injection')
|
|
254
|
-
break
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Check for XSS
|
|
260
|
-
if (this.options.attackPrevention.xss) {
|
|
261
|
-
for (const pattern of this.xssPatterns) {
|
|
262
|
-
if (pattern.test(input)) {
|
|
263
|
-
threats.push('XSS')
|
|
264
|
-
break
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Check for path traversal
|
|
270
|
-
if (this.options.attackPrevention.pathTraversal) {
|
|
271
|
-
for (const pattern of this.pathTraversalPatterns) {
|
|
272
|
-
if (pattern.test(input)) {
|
|
273
|
-
threats.push('Path Traversal')
|
|
274
|
-
break
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Check for command injection
|
|
280
|
-
if (this.options.attackPrevention.commandInjection) {
|
|
281
|
-
for (const pattern of this.commandInjectionPatterns) {
|
|
282
|
-
if (pattern.test(input)) {
|
|
283
|
-
threats.push('Command Injection')
|
|
284
|
-
break
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Check custom patterns
|
|
290
|
-
if (this.options.attackPrevention.customPatterns) {
|
|
291
|
-
for (const customPattern of this.options.attackPrevention.customPatterns) {
|
|
292
|
-
if (customPattern.pattern.test(input)) {
|
|
293
|
-
threats.push(customPattern.name)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return { isValid: threats.length === 0, threats }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private sanitizeInput(input: string): string {
|
|
302
|
-
if (!this.options.inputValidation?.sanitizeInput) {
|
|
303
|
-
return input
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return input
|
|
307
|
-
.replace(/[<>'"&]/g, (match) => {
|
|
308
|
-
const entities: Record<string, string> = {
|
|
309
|
-
'<': '<',
|
|
310
|
-
'>': '>',
|
|
311
|
-
'"': '"',
|
|
312
|
-
'\'': ''',
|
|
313
|
-
'&': '&',
|
|
314
|
-
}
|
|
315
|
-
return entities[match] || match
|
|
316
|
-
})
|
|
317
|
-
.replace(/javascript:/gi, 'blocked:')
|
|
318
|
-
.replace(/vbscript:/gi, 'blocked:')
|
|
319
|
-
.replace(/on\w+=/gi, 'blocked=')
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
323
|
-
const clientIP = this.getClientIP(req)
|
|
324
|
-
const userAgent = req.headers.get('user-agent') || ''
|
|
325
|
-
const url = new URL(req.url)
|
|
326
|
-
|
|
327
|
-
// IP filtering
|
|
328
|
-
if (this.options.ipFiltering?.enabled) {
|
|
329
|
-
const ipConfig = this.options.ipFiltering
|
|
330
|
-
|
|
331
|
-
// Check whitelist
|
|
332
|
-
if (ipConfig.whitelist?.length && !ipConfig.whitelist.includes(clientIP)) {
|
|
333
|
-
return new Response('Access Denied', { status: 403 })
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Check blacklist
|
|
337
|
-
if (ipConfig.blacklist?.includes(clientIP)) {
|
|
338
|
-
return new Response('Access Denied', { status: 403 })
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Block private IPs if configured
|
|
342
|
-
if (ipConfig.blockPrivateIPs && this.isPrivateIP(clientIP)) {
|
|
343
|
-
return new Response('Access Denied', { status: 403 })
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Request filtering
|
|
348
|
-
if (this.options.requestFiltering?.enabled) {
|
|
349
|
-
const filterConfig = this.options.requestFiltering
|
|
350
|
-
|
|
351
|
-
// Check allowed methods
|
|
352
|
-
if (filterConfig.allowedMethods && !filterConfig.allowedMethods.includes(req.method)) {
|
|
353
|
-
return new Response('Method Not Allowed', { status: 405 })
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Check user agent requirement
|
|
357
|
-
if (filterConfig.requireUserAgent && !userAgent) {
|
|
358
|
-
return new Response('Bad Request', { status: 400 })
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Check blocked user agents
|
|
362
|
-
if (filterConfig.blockUserAgents) {
|
|
363
|
-
const isBlocked = filterConfig.blockUserAgents.some(blocked =>
|
|
364
|
-
userAgent.toLowerCase().includes(blocked.toLowerCase()),
|
|
365
|
-
)
|
|
366
|
-
if (isBlocked) {
|
|
367
|
-
return new Response('Access Denied', { status: 403 })
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Check suspicious patterns in URL
|
|
372
|
-
if (filterConfig.blockSuspiciousPatterns) {
|
|
373
|
-
const fullUrl = url.pathname + url.search
|
|
374
|
-
for (const pattern of this.suspiciousPatterns) {
|
|
375
|
-
if (pattern.test(fullUrl)) {
|
|
376
|
-
return new Response('Suspicious Request Blocked', { status: 400 })
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Input validation
|
|
383
|
-
if (this.options.inputValidation?.enabled) {
|
|
384
|
-
const inputConfig = this.options.inputValidation
|
|
385
|
-
|
|
386
|
-
// Check URL length
|
|
387
|
-
if (inputConfig.maxUrlLength && req.url.length > inputConfig.maxUrlLength) {
|
|
388
|
-
return new Response('URL Too Long', { status: 414 })
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Check content type
|
|
392
|
-
const contentType = req.headers.get('content-type')
|
|
393
|
-
if (contentType && inputConfig.allowedContentTypes) {
|
|
394
|
-
const isAllowed = inputConfig.allowedContentTypes.some(allowed =>
|
|
395
|
-
contentType.toLowerCase().startsWith(allowed.toLowerCase()),
|
|
396
|
-
)
|
|
397
|
-
if (!isAllowed) {
|
|
398
|
-
return new Response('Unsupported Media Type', { status: 415 })
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Validate query parameters
|
|
403
|
-
for (const [key, value] of url.searchParams.entries()) {
|
|
404
|
-
const validation = this.validateInput(value)
|
|
405
|
-
if (!validation.isValid) {
|
|
406
|
-
console.warn(`Security threat detected in query param ${key}:`, validation.threats)
|
|
407
|
-
return new Response(`Security threat detected: ${validation.threats.join(', ')}`, { status: 400 })
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Validate headers
|
|
412
|
-
for (const [key, value] of req.headers.entries()) {
|
|
413
|
-
if (inputConfig.maxHeaderSize && value.length > inputConfig.maxHeaderSize) {
|
|
414
|
-
return new Response('Header Too Large', { status: 431 })
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const validation = this.validateInput(value)
|
|
418
|
-
if (!validation.isValid) {
|
|
419
|
-
console.warn(`Security threat detected in header ${key}:`, validation.threats)
|
|
420
|
-
return new Response(`Security threat detected: ${validation.threats.join(', ')}`, { status: 400 })
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Process request body if present
|
|
426
|
-
if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
|
|
427
|
-
try {
|
|
428
|
-
const contentLength = req.headers.get('content-length')
|
|
429
|
-
if (contentLength && this.options.inputValidation?.maxBodySize) {
|
|
430
|
-
const size = Number.parseInt(contentLength, 10)
|
|
431
|
-
if (size > this.options.inputValidation.maxBodySize) {
|
|
432
|
-
return new Response('Payload Too Large', { status: 413 })
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
console.error('Error checking content length:', error)
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const response = await next()
|
|
442
|
-
if (!response) {
|
|
443
|
-
return new Response('Not Found', { status: 404 })
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Response security
|
|
447
|
-
if (this.options.responseSecurity?.enabled) {
|
|
448
|
-
const headers = new Headers(response.headers)
|
|
449
|
-
const responseConfig = this.options.responseSecurity
|
|
450
|
-
|
|
451
|
-
// Remove server headers
|
|
452
|
-
if (responseConfig.removeServerHeaders) {
|
|
453
|
-
headers.delete('Server')
|
|
454
|
-
headers.delete('X-Powered-By')
|
|
455
|
-
headers.delete('X-AspNet-Version')
|
|
456
|
-
headers.delete('X-AspNetMvc-Version')
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Add security headers
|
|
460
|
-
if (responseConfig.addSecurityHeaders) {
|
|
461
|
-
headers.set('X-Content-Type-Options', 'nosniff')
|
|
462
|
-
headers.set('X-Frame-Options', 'DENY')
|
|
463
|
-
headers.set('X-XSS-Protection', '0')
|
|
464
|
-
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return new Response(response.body, {
|
|
468
|
-
status: response.status,
|
|
469
|
-
statusText: response.statusText,
|
|
470
|
-
headers,
|
|
471
|
-
})
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return response
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Factory function for easy use
|
|
479
|
-
export function security(options: SecurityOptions = {}): (req: EnhancedRequest, next: NextFunction) => Promise<Response> {
|
|
480
|
-
const instance = new Security(options)
|
|
481
|
-
return async (req: EnhancedRequest, next: NextFunction) => instance.handle(req, next)
|
|
482
|
-
}
|