@arcis/node 1.0.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.
Files changed (52) hide show
  1. package/README.md +222 -0
  2. package/dist/core/index.d.mts +170 -0
  3. package/dist/core/index.d.ts +170 -0
  4. package/dist/core/index.js +327 -0
  5. package/dist/core/index.js.map +1 -0
  6. package/dist/core/index.mjs +307 -0
  7. package/dist/core/index.mjs.map +1 -0
  8. package/dist/headers-BJq2OA0i.d.ts +284 -0
  9. package/dist/headers-DBQedhrb.d.mts +284 -0
  10. package/dist/index-BgHPM7LC.d.ts +129 -0
  11. package/dist/index-BpT7flAQ.d.ts +255 -0
  12. package/dist/index-JaFOUKyK.d.mts +255 -0
  13. package/dist/index-nAgXexwD.d.mts +129 -0
  14. package/dist/index.d.mts +139 -0
  15. package/dist/index.d.ts +139 -0
  16. package/dist/index.js +1860 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.mjs +1797 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/logging/index.d.mts +38 -0
  21. package/dist/logging/index.d.ts +38 -0
  22. package/dist/logging/index.js +140 -0
  23. package/dist/logging/index.js.map +1 -0
  24. package/dist/logging/index.mjs +136 -0
  25. package/dist/logging/index.mjs.map +1 -0
  26. package/dist/middleware/index.d.mts +3 -0
  27. package/dist/middleware/index.d.ts +3 -0
  28. package/dist/middleware/index.js +1173 -0
  29. package/dist/middleware/index.js.map +1 -0
  30. package/dist/middleware/index.mjs +1156 -0
  31. package/dist/middleware/index.mjs.map +1 -0
  32. package/dist/sanitizers/index.d.mts +24 -0
  33. package/dist/sanitizers/index.d.ts +24 -0
  34. package/dist/sanitizers/index.js +610 -0
  35. package/dist/sanitizers/index.js.map +1 -0
  36. package/dist/sanitizers/index.mjs +587 -0
  37. package/dist/sanitizers/index.mjs.map +1 -0
  38. package/dist/stores/index.d.mts +106 -0
  39. package/dist/stores/index.d.ts +106 -0
  40. package/dist/stores/index.js +149 -0
  41. package/dist/stores/index.js.map +1 -0
  42. package/dist/stores/index.mjs +145 -0
  43. package/dist/stores/index.mjs.map +1 -0
  44. package/dist/types-BOdL3ZWo.d.mts +264 -0
  45. package/dist/types-BOdL3ZWo.d.ts +264 -0
  46. package/dist/validation/index.d.mts +3 -0
  47. package/dist/validation/index.d.ts +3 -0
  48. package/dist/validation/index.js +705 -0
  49. package/dist/validation/index.js.map +1 -0
  50. package/dist/validation/index.mjs +699 -0
  51. package/dist/validation/index.mjs.map +1 -0
  52. package/package.json +109 -0
@@ -0,0 +1,1156 @@
1
+ // src/core/constants.ts
2
+ var INPUT = {
3
+ /** Default maximum input size (1MB) */
4
+ DEFAULT_MAX_SIZE: 1e6,
5
+ /** Maximum recursion depth for nested objects */
6
+ MAX_RECURSION_DEPTH: 10
7
+ };
8
+ var RATE_LIMIT = {
9
+ /** Default window size (1 minute) */
10
+ DEFAULT_WINDOW_MS: 6e4,
11
+ /** Default max requests per window */
12
+ DEFAULT_MAX_REQUESTS: 100,
13
+ /** Default HTTP status code for rate limited responses */
14
+ DEFAULT_STATUS_CODE: 429,
15
+ /** Default error message */
16
+ DEFAULT_MESSAGE: "Too many requests, please try again later."};
17
+ var HEADERS = {
18
+ /** Default Content Security Policy */
19
+ DEFAULT_CSP: [
20
+ "default-src 'self'",
21
+ "script-src 'self'",
22
+ "style-src 'self' 'unsafe-inline'",
23
+ "img-src 'self' data: https:",
24
+ "font-src 'self'",
25
+ "object-src 'none'",
26
+ "frame-ancestors 'none'"
27
+ ].join("; "),
28
+ /** Default HSTS max age (1 year in seconds) */
29
+ HSTS_MAX_AGE: 31536e3,
30
+ /** Default X-Frame-Options value */
31
+ FRAME_OPTIONS: "DENY",
32
+ /** Default X-Content-Type-Options value */
33
+ CONTENT_TYPE_OPTIONS: "nosniff",
34
+ /** Default Referrer-Policy value */
35
+ REFERRER_POLICY: "strict-origin-when-cross-origin",
36
+ /** Default Permissions-Policy value */
37
+ PERMISSIONS_POLICY: "geolocation=(), microphone=(), camera=()",
38
+ /** Default Cache-Control value for security */
39
+ CACHE_CONTROL: "no-store, no-cache, must-revalidate, proxy-revalidate"
40
+ };
41
+ var XSS_REMOVE_PATTERNS = [
42
+ /** Full script blocks (content + tags) */
43
+ /<script[^>]*>[\s\S]*?<\/script>/gi,
44
+ /** Standalone/unclosed script tags */
45
+ /<script[^>]*>/gi,
46
+ /** iframe — full block and partial/unclosed */
47
+ /<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
48
+ /<iframe[^>]*/gi,
49
+ /** object — full block and partial/unclosed */
50
+ /<object[^>]*>[\s\S]*?<\/object>/gi,
51
+ /<object[^>]*/gi,
52
+ /** embed tags */
53
+ /<embed[^>]*/gi,
54
+ /** SVG with inline event handlers */
55
+ /<svg[^>]*onload[^>]*>/gi,
56
+ /** URL-encoded script tags */
57
+ /%3Cscript/gi,
58
+ /** Event handlers with quoted values: onclick="...", onerror='...' */
59
+ /(?:[\s/])on\w+\s*=\s*["'][^"']*["']/gi,
60
+ /** Event handlers with unquoted values: onload=value */
61
+ /(?:[\s/])on\w+\s*=\s*[^\s>]*/gi,
62
+ /** javascript: and vbscript: protocols (allow optional spaces before colon) */
63
+ /javascript\s*:/gi,
64
+ /vbscript\s*:/gi,
65
+ /** data: URIs with HTML/script content */
66
+ /data\s*:\s*text\/html[^>\s]*/gi
67
+ ];
68
+ var SQL_PATTERNS = [
69
+ /** SQL keywords */
70
+ /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|TRUNCATE|EXEC|EXECUTE)\b)/gi,
71
+ /** SQL comments: ANSI (--), C-style (slash-star ... star-slash), MySQL (#) */
72
+ /(--|\/\*|\*\/|#)/g,
73
+ /** SQL statement separators */
74
+ /(;|\|\||&&)/g,
75
+ /** Boolean injection: OR 1=1 */
76
+ /\bOR\s+\d+\s*=\s*\d+/gi,
77
+ /** Boolean injection: OR 'a'='a' or OR "a"="a" (including mixed quotes) */
78
+ /\bOR\s+(['"])[^'"]*\1\s*=\s*(['"])[^'"]*\2/gi,
79
+ /\bOR\s+('[^']*'|"[^"]*")\s*=\s*('[^']*'|"[^"]*")/gi,
80
+ /** Boolean injection: AND 1=1 */
81
+ /\bAND\s+\d+\s*=\s*\d+/gi,
82
+ /** Boolean injection: AND 'a'='a' or AND "a"="a" (including mixed quotes) */
83
+ /\bAND\s+(['"])[^'"]*\1\s*=\s*(['"])[^'"]*\2/gi,
84
+ /\bAND\s+('[^']*'|"[^"]*")\s*=\s*('[^']*'|"[^"]*")/gi,
85
+ /** Time-based blind: SLEEP() */
86
+ /\bSLEEP\s*\(\s*\d+\s*\)/gi,
87
+ /** Time-based blind: BENCHMARK() */
88
+ /\bBENCHMARK\s*\(/gi
89
+ ];
90
+ var PATH_PATTERNS = [
91
+ /** Unix path traversal */
92
+ /\.\.\//g,
93
+ /** Windows path traversal */
94
+ /\.\.\\/g,
95
+ /** URL-encoded traversal (%2e%2e) */
96
+ /%2e%2e/gi,
97
+ /** Double URL-encoded traversal (%252e) */
98
+ /%252e/gi,
99
+ /** Mixed encoding: ..%2F */
100
+ /\.\.%2F/gi,
101
+ /** Mixed encoding: %2e./ and .%2e/ */
102
+ /%2e\.[\\/]/gi,
103
+ /\.%2e[\\/]/gi,
104
+ /** Fully URL-encoded: %2e%2e%2f */
105
+ /%2e%2e%2f/gi,
106
+ /** Null byte injection in paths */
107
+ /\0/g
108
+ ];
109
+ var COMMAND_PATTERNS = [
110
+ /**
111
+ * Shell metacharacters that enable command chaining/substitution.
112
+ * Bare ( and ) are excluded — they appear in common legitimate values
113
+ * (function calls in code fields, math expressions, etc.).
114
+ * Command substitution is caught by the $( combined pattern below.
115
+ * NOTE: ';', '&', '|' may appear in legitimate URL query strings
116
+ * and Markdown; consider disabling command checking (command: false)
117
+ * for fields that intentionally allow those characters.
118
+ */
119
+ /[;&|`]/g,
120
+ /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
121
+ /\$\(/g
122
+ ];
123
+ var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
124
+ "__proto__",
125
+ "constructor",
126
+ "prototype",
127
+ "__definegetter__",
128
+ "__definesetter__",
129
+ "__lookupgetter__",
130
+ "__lookupsetter__"
131
+ ]);
132
+ var NOSQL_DANGEROUS_KEYS = /* @__PURE__ */ new Set([
133
+ // Comparison
134
+ "$gt",
135
+ "$gte",
136
+ "$lt",
137
+ "$lte",
138
+ "$ne",
139
+ "$eq",
140
+ "$in",
141
+ "$nin",
142
+ // Logical
143
+ "$and",
144
+ "$or",
145
+ "$not",
146
+ "$nor",
147
+ // Element / evaluation
148
+ "$exists",
149
+ "$type",
150
+ "$regex",
151
+ "$where",
152
+ "$expr",
153
+ "$mod",
154
+ "$text",
155
+ // Array
156
+ "$elemMatch",
157
+ "$all",
158
+ "$size",
159
+ // JavaScript execution (critical)
160
+ "$function",
161
+ "$accumulator",
162
+ // Aggregation pipeline operators (injectable via $lookup etc.)
163
+ "$lookup",
164
+ "$match",
165
+ "$project",
166
+ "$group",
167
+ "$sort",
168
+ "$limit",
169
+ "$skip",
170
+ "$unwind",
171
+ "$addFields",
172
+ "$replaceRoot"
173
+ ]);
174
+ var REDACTION = {
175
+ /** Replacement text for redacted values */
176
+ REPLACEMENT: "[REDACTED]",
177
+ /** Truncation indicator */
178
+ TRUNCATED: "[TRUNCATED]",
179
+ /** Max depth indicator */
180
+ MAX_DEPTH: "[MAX_DEPTH]",
181
+ /** Default max message length */
182
+ DEFAULT_MAX_LENGTH: 1e4,
183
+ /** Default sensitive keys to redact */
184
+ SENSITIVE_KEYS: /* @__PURE__ */ new Set([
185
+ "password",
186
+ "passwd",
187
+ "pwd",
188
+ "secret",
189
+ "token",
190
+ "apikey",
191
+ "api_key",
192
+ "apiKey",
193
+ "auth",
194
+ "authorization",
195
+ "credit_card",
196
+ "creditcard",
197
+ "cc",
198
+ "ssn",
199
+ "social_security",
200
+ "private_key",
201
+ "privateKey",
202
+ "access_token",
203
+ "accessToken",
204
+ "refresh_token",
205
+ "refreshToken",
206
+ "bearer",
207
+ "jwt",
208
+ "session",
209
+ "cookie",
210
+ "credentials",
211
+ "x-api-key",
212
+ "x-auth-token"
213
+ ])
214
+ };
215
+ var VALIDATION = {
216
+ /**
217
+ * Email regex pattern.
218
+ * Rejects consecutive dots in local part (e.g. test..foo@example.com),
219
+ * leading/trailing dots, and other common invalid forms.
220
+ */
221
+ EMAIL: /^[^\s@.][^\s@]*(?:\.[^\s@.][^\s@]*)*@[^\s@]+\.[^\s@]+$/,
222
+ /**
223
+ * URL regex pattern.
224
+ * Only allows http:// and https:// — explicitly rejects javascript:,
225
+ * data:, vbscript:, and other dangerous URI schemes.
226
+ */
227
+ URL: /^https?:\/\/[^\s/$.?#][^\s]*$/,
228
+ /** UUID regex pattern (v4) */
229
+ UUID: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
230
+ };
231
+ var ERRORS = {
232
+ /** Generic error message (production) */
233
+ INTERNAL_SERVER_ERROR: "Internal Server Error",
234
+ /** Validation error messages */
235
+ VALIDATION: {
236
+ REQUIRED: (field) => `${field} is required`,
237
+ INVALID_TYPE: (field, type) => `${field} must be a ${type}`,
238
+ MIN_LENGTH: (field, min) => `${field} must be at least ${min} characters`,
239
+ MAX_LENGTH: (field, max) => `${field} must be at most ${max} characters`,
240
+ MIN_VALUE: (field, min) => `${field} must be at least ${min}`,
241
+ MAX_VALUE: (field, max) => `${field} must be at most ${max}`,
242
+ INVALID_FORMAT: (field) => `${field} format is invalid`,
243
+ INVALID_EMAIL: (field) => `${field} must be a valid email`,
244
+ INVALID_URL: (field) => `${field} must be a valid URL`,
245
+ INVALID_UUID: (field) => `${field} must be a valid UUID`,
246
+ INVALID_ENUM: (field, values) => `${field} must be one of: ${values.join(", ")}`,
247
+ MIN_ITEMS: (field, min) => `${field} must have at least ${min} items`,
248
+ MAX_ITEMS: (field, max) => `${field} must have at most ${max} items`
249
+ }
250
+ };
251
+
252
+ // src/middleware/headers.ts
253
+ function createHeaders(options = {}) {
254
+ const {
255
+ contentSecurityPolicy = true,
256
+ xssFilter = true,
257
+ noSniff = true,
258
+ frameOptions = HEADERS.FRAME_OPTIONS,
259
+ hsts = true,
260
+ referrerPolicy = HEADERS.REFERRER_POLICY,
261
+ permissionsPolicy = HEADERS.PERMISSIONS_POLICY,
262
+ cacheControl = true
263
+ } = options;
264
+ return (req, res, next) => {
265
+ if (contentSecurityPolicy) {
266
+ const csp = typeof contentSecurityPolicy === "string" ? contentSecurityPolicy : HEADERS.DEFAULT_CSP;
267
+ res.setHeader("Content-Security-Policy", csp);
268
+ }
269
+ if (xssFilter) {
270
+ res.setHeader("X-XSS-Protection", "1; mode=block");
271
+ }
272
+ if (noSniff) {
273
+ res.setHeader("X-Content-Type-Options", HEADERS.CONTENT_TYPE_OPTIONS);
274
+ }
275
+ if (frameOptions) {
276
+ res.setHeader("X-Frame-Options", frameOptions);
277
+ }
278
+ const forwardedProto = req.headers["x-forwarded-proto"]?.split(",")[0].trim().toLowerCase();
279
+ const trustedForwardedProto = forwardedProto === "https" || forwardedProto === "http" ? forwardedProto : void 0;
280
+ const isHttps = req.secure || trustedForwardedProto === "https";
281
+ if (hsts && isHttps) {
282
+ const hstsOpts = typeof hsts === "object" ? hsts : {};
283
+ const maxAge = hstsOpts.maxAge ?? HEADERS.HSTS_MAX_AGE;
284
+ const includeSubDomains = hstsOpts.includeSubDomains !== false;
285
+ const preload = hstsOpts.preload === true;
286
+ let hstsValue = `max-age=${maxAge}`;
287
+ if (includeSubDomains) hstsValue += "; includeSubDomains";
288
+ if (preload) hstsValue += "; preload";
289
+ res.setHeader("Strict-Transport-Security", hstsValue);
290
+ }
291
+ if (referrerPolicy) {
292
+ res.setHeader("Referrer-Policy", referrerPolicy);
293
+ }
294
+ if (permissionsPolicy) {
295
+ res.setHeader("Permissions-Policy", permissionsPolicy);
296
+ }
297
+ res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
298
+ if (cacheControl) {
299
+ const cacheControlValue = typeof cacheControl === "string" ? cacheControl : HEADERS.CACHE_CONTROL;
300
+ res.setHeader("Cache-Control", cacheControlValue);
301
+ res.setHeader("Pragma", "no-cache");
302
+ res.setHeader("Expires", "0");
303
+ }
304
+ res.removeHeader("X-Powered-By");
305
+ next();
306
+ };
307
+ }
308
+ var securityHeaders = createHeaders;
309
+
310
+ // src/middleware/rate-limit.ts
311
+ function createRateLimiter(options = {}) {
312
+ const {
313
+ max = RATE_LIMIT.DEFAULT_MAX_REQUESTS,
314
+ windowMs = RATE_LIMIT.DEFAULT_WINDOW_MS,
315
+ message = RATE_LIMIT.DEFAULT_MESSAGE,
316
+ statusCode = RATE_LIMIT.DEFAULT_STATUS_CODE,
317
+ keyGenerator = (req) => {
318
+ const ip = req.ip ?? req.socket?.remoteAddress;
319
+ if (!ip) {
320
+ console.warn(
321
+ "[arcis] Rate limiter: cannot resolve client IP. All unresolvable clients share one counter. Set Express trust proxy if behind a reverse proxy."
322
+ );
323
+ return "unknown";
324
+ }
325
+ return ip;
326
+ },
327
+ skip,
328
+ store: externalStore
329
+ } = options;
330
+ const inMemoryStore = /* @__PURE__ */ Object.create(null);
331
+ let cleanupInterval = null;
332
+ if (!externalStore) {
333
+ cleanupInterval = setInterval(() => {
334
+ const now = Date.now();
335
+ for (const key of Object.keys(inMemoryStore)) {
336
+ if (inMemoryStore[key].resetTime < now) {
337
+ delete inMemoryStore[key];
338
+ }
339
+ }
340
+ }, windowMs);
341
+ if (typeof cleanupInterval.unref === "function") {
342
+ cleanupInterval.unref();
343
+ }
344
+ }
345
+ const handler = async (req, res, next) => {
346
+ try {
347
+ if (skip?.(req)) {
348
+ return next();
349
+ }
350
+ const key = keyGenerator(req);
351
+ const now = Date.now();
352
+ let count;
353
+ let resetTime;
354
+ if (externalStore) {
355
+ const entry = await externalStore.get(key);
356
+ if (!entry || entry.resetTime < now) {
357
+ await externalStore.set(key, { count: 1, resetTime: now + windowMs });
358
+ count = 1;
359
+ resetTime = now + windowMs;
360
+ } else {
361
+ count = await externalStore.increment(key);
362
+ resetTime = entry.resetTime;
363
+ }
364
+ } else {
365
+ if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
366
+ inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
367
+ } else {
368
+ inMemoryStore[key].count++;
369
+ }
370
+ count = inMemoryStore[key].count;
371
+ resetTime = inMemoryStore[key].resetTime;
372
+ }
373
+ const remaining = Math.max(0, max - count);
374
+ const resetSeconds = Math.ceil((resetTime - now) / 1e3);
375
+ res.setHeader("X-RateLimit-Limit", max.toString());
376
+ res.setHeader("X-RateLimit-Remaining", remaining.toString());
377
+ res.setHeader("X-RateLimit-Reset", resetSeconds.toString());
378
+ if (count > max) {
379
+ res.setHeader("Retry-After", resetSeconds.toString());
380
+ res.status(statusCode).json({
381
+ error: message,
382
+ retryAfter: resetSeconds
383
+ });
384
+ return;
385
+ }
386
+ next();
387
+ } catch (error) {
388
+ console.error("[arcis] Rate limiter error:", error);
389
+ next();
390
+ }
391
+ };
392
+ const middleware = handler;
393
+ middleware.close = () => {
394
+ if (cleanupInterval) {
395
+ clearInterval(cleanupInterval);
396
+ cleanupInterval = null;
397
+ }
398
+ };
399
+ return middleware;
400
+ }
401
+ var rateLimit = createRateLimiter;
402
+
403
+ // src/middleware/error-handler.ts
404
+ var SENSITIVE_ERROR_PATTERNS = [
405
+ // SQL database errors
406
+ /\b(SQLITE_ERROR|SQLSTATE|ORA-\d|PG::|mysql_|pg_query|ECONNREFUSED)/i,
407
+ /\b(syntax error at or near|relation ".*" does not exist)/i,
408
+ /\b(column ".*" (does not exist|of relation))/i,
409
+ /\b(duplicate key value violates unique constraint)/i,
410
+ /\b(table .* doesn't exist|unknown column)/i,
411
+ // MongoDB errors
412
+ /\b(MongoError|MongoServerError|MongoNetworkError|E11000 duplicate key)/i,
413
+ // Redis errors
414
+ /\b(WRONGTYPE|CROSSSLOT|CLUSTERDOWN|READONLY|ReplyError)/i,
415
+ // Connection strings and DSNs
416
+ /\b(mongodb(\+srv)?:\/\/|postgres(ql)?:\/\/|mysql:\/\/|redis:\/\/)/i,
417
+ // Stack traces with file paths
418
+ /\bat\s+.*\.(js|ts|py|go|java):\d+/i,
419
+ // Internal IP addresses
420
+ /\b(127\.0\.0\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+)\b/
421
+ ];
422
+ function containsSensitiveInfo(message) {
423
+ return SENSITIVE_ERROR_PATTERNS.some((pattern) => pattern.test(message));
424
+ }
425
+ function errorHandler(options = false) {
426
+ const isDev = typeof options === "boolean" ? options : options.isDev ?? false;
427
+ const logErrors = typeof options === "object" ? options.logErrors ?? true : true;
428
+ const logger = typeof options === "object" ? options.logger : void 0;
429
+ const customHandler = typeof options === "object" ? options.customHandler : void 0;
430
+ return (err, req, res, _next) => {
431
+ const statusCode = err.statusCode || err.status || 500;
432
+ if (customHandler) {
433
+ return customHandler(err, req, res);
434
+ }
435
+ if (logErrors) {
436
+ const logData = {
437
+ error: err.message,
438
+ stack: err.stack,
439
+ statusCode,
440
+ path: req.path,
441
+ method: req.method
442
+ };
443
+ if (logger) {
444
+ logger.error("Request error", logData);
445
+ } else {
446
+ console.error("[arcis] Request error:", logData);
447
+ }
448
+ }
449
+ const exposeMessage = isDev || err.expose === true;
450
+ let clientMessage;
451
+ if (!exposeMessage) {
452
+ clientMessage = ERRORS.INTERNAL_SERVER_ERROR;
453
+ } else if (containsSensitiveInfo(err.message)) {
454
+ clientMessage = isDev ? err.message : ERRORS.INTERNAL_SERVER_ERROR;
455
+ } else {
456
+ clientMessage = err.message;
457
+ }
458
+ const response = {
459
+ error: clientMessage
460
+ };
461
+ if (isDev) {
462
+ response.stack = err.stack;
463
+ response.details = err.message;
464
+ }
465
+ res.status(statusCode).json(response);
466
+ };
467
+ }
468
+ var createErrorHandler = errorHandler;
469
+
470
+ // src/core/errors.ts
471
+ var ArcisError = class extends Error {
472
+ constructor(message, statusCode = 500, code = "ARCIS_ERROR") {
473
+ super(message);
474
+ this.name = "ArcisError";
475
+ this.statusCode = statusCode;
476
+ this.code = code;
477
+ this.expose = statusCode < 500;
478
+ if (Error.captureStackTrace) {
479
+ Error.captureStackTrace(this, this.constructor);
480
+ }
481
+ }
482
+ };
483
+ var InputTooLargeError = class extends ArcisError {
484
+ constructor(maxSize, actualSize) {
485
+ super(`Input exceeds maximum size of ${maxSize} bytes`, 413, "INPUT_TOO_LARGE");
486
+ this.name = "InputTooLargeError";
487
+ this.maxSize = maxSize;
488
+ this.actualSize = actualSize;
489
+ }
490
+ };
491
+ var SecurityThreatError = class extends ArcisError {
492
+ constructor(threatType, pattern) {
493
+ super("Request blocked for security reasons", 400, "SECURITY_THREAT");
494
+ this.name = "SecurityThreatError";
495
+ this.threatType = threatType;
496
+ this.pattern = pattern;
497
+ }
498
+ };
499
+
500
+ // src/sanitizers/utils.ts
501
+ function encodeHtmlEntities(str) {
502
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
503
+ }
504
+
505
+ // src/sanitizers/xss.ts
506
+ function sanitizeXss(input, collectThreats = false, htmlEncode = false) {
507
+ if (typeof input !== "string") {
508
+ return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
509
+ }
510
+ const threats = [];
511
+ let value = input;
512
+ let wasSanitized = false;
513
+ for (const pattern of XSS_REMOVE_PATTERNS) {
514
+ pattern.lastIndex = 0;
515
+ if (pattern.test(value)) {
516
+ pattern.lastIndex = 0;
517
+ if (collectThreats) {
518
+ const matches = value.match(pattern);
519
+ if (matches) {
520
+ for (const match of matches) {
521
+ threats.push({
522
+ type: "xss",
523
+ pattern: pattern.source,
524
+ original: match
525
+ });
526
+ }
527
+ }
528
+ }
529
+ value = value.replace(pattern, "");
530
+ wasSanitized = true;
531
+ }
532
+ }
533
+ if (htmlEncode) {
534
+ const encoded = encodeHtmlEntities(value);
535
+ if (encoded !== value) {
536
+ wasSanitized = true;
537
+ }
538
+ value = encoded;
539
+ }
540
+ if (collectThreats) {
541
+ return { value, wasSanitized, threats };
542
+ }
543
+ return value;
544
+ }
545
+
546
+ // src/sanitizers/sql.ts
547
+ function sanitizeSql(input, collectThreats = false) {
548
+ if (typeof input !== "string") {
549
+ return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
550
+ }
551
+ const threats = [];
552
+ let value = input;
553
+ let wasSanitized = false;
554
+ for (const pattern of SQL_PATTERNS) {
555
+ pattern.lastIndex = 0;
556
+ if (pattern.test(value)) {
557
+ pattern.lastIndex = 0;
558
+ if (collectThreats) {
559
+ const matches = value.match(pattern);
560
+ if (matches) {
561
+ for (const match of matches) {
562
+ threats.push({
563
+ type: "sql_injection",
564
+ pattern: pattern.source,
565
+ original: match
566
+ });
567
+ }
568
+ }
569
+ }
570
+ value = value.replace(pattern, " ");
571
+ wasSanitized = true;
572
+ }
573
+ }
574
+ if (collectThreats) {
575
+ return { value, wasSanitized, threats };
576
+ }
577
+ return value;
578
+ }
579
+ function detectSql(input) {
580
+ if (typeof input !== "string") return false;
581
+ for (const pattern of SQL_PATTERNS) {
582
+ pattern.lastIndex = 0;
583
+ if (pattern.test(input)) {
584
+ return true;
585
+ }
586
+ }
587
+ return false;
588
+ }
589
+
590
+ // src/sanitizers/path.ts
591
+ function sanitizePath(input, collectThreats = false) {
592
+ if (typeof input !== "string") {
593
+ return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
594
+ }
595
+ const threats = [];
596
+ let value = input;
597
+ let wasSanitized = false;
598
+ for (const pattern of PATH_PATTERNS) {
599
+ pattern.lastIndex = 0;
600
+ if (pattern.test(value)) {
601
+ pattern.lastIndex = 0;
602
+ if (collectThreats) {
603
+ const matches = value.match(pattern);
604
+ if (matches) {
605
+ for (const match of matches) {
606
+ threats.push({
607
+ type: "path_traversal",
608
+ pattern: pattern.source,
609
+ original: match
610
+ });
611
+ }
612
+ }
613
+ }
614
+ value = value.replace(pattern, "");
615
+ wasSanitized = true;
616
+ }
617
+ }
618
+ if (collectThreats) {
619
+ return { value, wasSanitized, threats };
620
+ }
621
+ return value;
622
+ }
623
+
624
+ // src/sanitizers/command.ts
625
+ function sanitizeCommand(input, collectThreats = false) {
626
+ if (typeof input !== "string") {
627
+ return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
628
+ }
629
+ const threats = [];
630
+ let value = input;
631
+ let wasSanitized = false;
632
+ for (const pattern of COMMAND_PATTERNS) {
633
+ pattern.lastIndex = 0;
634
+ if (pattern.test(value)) {
635
+ pattern.lastIndex = 0;
636
+ if (collectThreats) {
637
+ const matches = value.match(pattern);
638
+ if (matches) {
639
+ for (const match of matches) {
640
+ threats.push({
641
+ type: "command_injection",
642
+ pattern: pattern.source,
643
+ original: match
644
+ });
645
+ }
646
+ }
647
+ }
648
+ value = value.replace(pattern, " ");
649
+ wasSanitized = true;
650
+ }
651
+ }
652
+ if (collectThreats) {
653
+ return { value, wasSanitized, threats };
654
+ }
655
+ return value;
656
+ }
657
+ function detectCommandInjection(input) {
658
+ if (typeof input !== "string") return false;
659
+ for (const pattern of COMMAND_PATTERNS) {
660
+ pattern.lastIndex = 0;
661
+ if (pattern.test(input)) {
662
+ return true;
663
+ }
664
+ }
665
+ return false;
666
+ }
667
+
668
+ // src/sanitizers/sanitize.ts
669
+ function sanitizeString(value, options = {}) {
670
+ if (typeof value !== "string") return value;
671
+ const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
672
+ if (value.length > maxSize) {
673
+ throw new InputTooLargeError(maxSize, value.length);
674
+ }
675
+ const reject = options.mode !== "sanitize";
676
+ let result = value;
677
+ if (options.sql !== false) {
678
+ if (reject) {
679
+ if (detectSql(result)) {
680
+ throw new SecurityThreatError("sql_injection", "SQL pattern detected in input");
681
+ }
682
+ } else {
683
+ result = sanitizeSql(result);
684
+ }
685
+ }
686
+ if (options.path !== false) {
687
+ result = sanitizePath(result);
688
+ }
689
+ if (options.command !== false) {
690
+ if (reject) {
691
+ if (detectCommandInjection(result)) {
692
+ throw new SecurityThreatError("command_injection", "Shell metacharacter detected in input");
693
+ }
694
+ } else {
695
+ result = sanitizeCommand(result);
696
+ }
697
+ }
698
+ if (options.xss !== false) {
699
+ result = sanitizeXss(result, false, options.htmlEncode ?? false);
700
+ }
701
+ return result;
702
+ }
703
+ function sanitizeObject(obj, options = {}) {
704
+ if (obj === null || obj === void 0) return obj;
705
+ if (typeof obj === "string") return sanitizeString(obj, options);
706
+ if (typeof obj !== "object") return obj;
707
+ if (Array.isArray(obj)) return obj.map((item) => sanitizeObject(item, options));
708
+ return sanitizeObjectDepth(obj, options, 0);
709
+ }
710
+ function sanitizeObjectDepth(obj, options, depth) {
711
+ if (depth >= INPUT.MAX_RECURSION_DEPTH) return obj;
712
+ const result = {};
713
+ for (const key of Object.keys(obj)) {
714
+ if (options.proto !== false && DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
715
+ continue;
716
+ }
717
+ if (options.nosql !== false && NOSQL_DANGEROUS_KEYS.has(key)) {
718
+ continue;
719
+ }
720
+ const sanitizedKey = sanitizeString(key, options);
721
+ const value = obj[key];
722
+ if (value === null || value === void 0) {
723
+ result[sanitizedKey] = value;
724
+ } else if (typeof value === "string") {
725
+ result[sanitizedKey] = sanitizeString(value, options);
726
+ } else if (Array.isArray(value)) {
727
+ result[sanitizedKey] = value.map((item) => sanitizeObject(item, options));
728
+ } else if (typeof value === "object") {
729
+ result[sanitizedKey] = sanitizeObjectDepth(value, options, depth + 1);
730
+ } else {
731
+ result[sanitizedKey] = value;
732
+ }
733
+ }
734
+ return result;
735
+ }
736
+ function createSanitizer(options = {}) {
737
+ return (req, _res, next) => {
738
+ try {
739
+ if (req.body && typeof req.body === "object") {
740
+ req.body = sanitizeObject(req.body, options);
741
+ }
742
+ if (req.query && typeof req.query === "object") {
743
+ const sanitizedQuery = sanitizeObject(req.query, options);
744
+ Object.defineProperty(req, "query", { value: sanitizedQuery, writable: true, configurable: true });
745
+ }
746
+ if (req.params && typeof req.params === "object") {
747
+ const sanitizedParams = sanitizeObject(req.params, options);
748
+ Object.defineProperty(req, "params", { value: sanitizedParams, writable: true, configurable: true });
749
+ }
750
+ next();
751
+ } catch (err) {
752
+ next(err);
753
+ }
754
+ };
755
+ }
756
+
757
+ // src/validation/schema.ts
758
+ function validate(schema, source = "body") {
759
+ return (req, res, next) => {
760
+ const data = req[source] || {};
761
+ const errors = [];
762
+ const validated = {};
763
+ for (const [field, rules] of Object.entries(schema)) {
764
+ const value = data[field];
765
+ const result = validateField(field, value, rules);
766
+ if (result.errors.length > 0) {
767
+ errors.push(...result.errors);
768
+ } else if (result.value !== void 0) {
769
+ validated[field] = result.value;
770
+ }
771
+ }
772
+ if (errors.length > 0) {
773
+ res.status(400).json({ errors });
774
+ return;
775
+ }
776
+ req[source] = validated;
777
+ next();
778
+ };
779
+ }
780
+ function validateField(field, value, rules) {
781
+ const errors = [];
782
+ if (rules.required && (value === void 0 || value === null || value === "")) {
783
+ errors.push(ERRORS.VALIDATION.REQUIRED(field));
784
+ return { errors };
785
+ }
786
+ if (value === void 0 || value === null) {
787
+ return { errors: [] };
788
+ }
789
+ let typedValue = value;
790
+ let isValid = true;
791
+ switch (rules.type) {
792
+ case "string":
793
+ if (typeof value !== "string") {
794
+ errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "string"));
795
+ isValid = false;
796
+ break;
797
+ }
798
+ if (rules.min !== void 0 && value.length < rules.min) {
799
+ errors.push(ERRORS.VALIDATION.MIN_LENGTH(field, rules.min));
800
+ isValid = false;
801
+ }
802
+ if (rules.max !== void 0 && value.length > rules.max) {
803
+ errors.push(ERRORS.VALIDATION.MAX_LENGTH(field, rules.max));
804
+ isValid = false;
805
+ }
806
+ if (rules.pattern && !rules.pattern.test(value)) {
807
+ errors.push(ERRORS.VALIDATION.INVALID_FORMAT(field));
808
+ isValid = false;
809
+ }
810
+ if (isValid && rules.enum && !rules.enum.includes(value)) {
811
+ errors.push(ERRORS.VALIDATION.INVALID_ENUM(field, rules.enum));
812
+ isValid = false;
813
+ }
814
+ if (isValid && rules.sanitize !== false) {
815
+ typedValue = sanitizeString(value);
816
+ }
817
+ break;
818
+ case "number":
819
+ typedValue = Number(value);
820
+ if (isNaN(typedValue)) {
821
+ errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "number"));
822
+ isValid = false;
823
+ break;
824
+ }
825
+ if (rules.min !== void 0 && typedValue < rules.min) {
826
+ errors.push(ERRORS.VALIDATION.MIN_VALUE(field, rules.min));
827
+ isValid = false;
828
+ }
829
+ if (rules.max !== void 0 && typedValue > rules.max) {
830
+ errors.push(ERRORS.VALIDATION.MAX_VALUE(field, rules.max));
831
+ isValid = false;
832
+ }
833
+ break;
834
+ case "boolean":
835
+ if (value === "true" || value === true || value === 1 || value === "1") {
836
+ typedValue = true;
837
+ } else if (value === "false" || value === false || value === 0 || value === "0") {
838
+ typedValue = false;
839
+ } else {
840
+ errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "boolean"));
841
+ isValid = false;
842
+ }
843
+ break;
844
+ case "email":
845
+ if (!VALIDATION.EMAIL.test(String(value))) {
846
+ errors.push(ERRORS.VALIDATION.INVALID_EMAIL(field));
847
+ isValid = false;
848
+ }
849
+ if (isValid) {
850
+ typedValue = sanitizeString(String(value).toLowerCase().trim());
851
+ }
852
+ break;
853
+ case "url":
854
+ if (!VALIDATION.URL.test(String(value))) {
855
+ errors.push(ERRORS.VALIDATION.INVALID_URL(field));
856
+ isValid = false;
857
+ }
858
+ if (isValid) {
859
+ typedValue = sanitizeString(String(value));
860
+ }
861
+ break;
862
+ case "uuid":
863
+ if (!VALIDATION.UUID.test(String(value))) {
864
+ errors.push(ERRORS.VALIDATION.INVALID_UUID(field));
865
+ isValid = false;
866
+ }
867
+ break;
868
+ case "array":
869
+ if (!Array.isArray(value)) {
870
+ errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "array"));
871
+ isValid = false;
872
+ break;
873
+ }
874
+ if (rules.min !== void 0 && value.length < rules.min) {
875
+ errors.push(ERRORS.VALIDATION.MIN_ITEMS(field, rules.min));
876
+ isValid = false;
877
+ }
878
+ if (rules.max !== void 0 && value.length > rules.max) {
879
+ errors.push(ERRORS.VALIDATION.MAX_ITEMS(field, rules.max));
880
+ isValid = false;
881
+ }
882
+ break;
883
+ case "object":
884
+ if (typeof value !== "object" || Array.isArray(value) || value === null) {
885
+ errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "object"));
886
+ isValid = false;
887
+ }
888
+ break;
889
+ }
890
+ if (isValid && rules.enum && rules.type !== "string" && !rules.enum.includes(typedValue)) {
891
+ errors.push(ERRORS.VALIDATION.INVALID_ENUM(field, rules.enum));
892
+ isValid = false;
893
+ }
894
+ if (isValid && rules.custom) {
895
+ const customResult = rules.custom(typedValue);
896
+ if (customResult === void 0) {
897
+ throw new TypeError(
898
+ `Custom validator for field "${field}" returned undefined. Return true to pass, false to fail, or a string error message.`
899
+ );
900
+ }
901
+ if (customResult !== true) {
902
+ errors.push(typeof customResult === "string" && customResult.length > 0 ? customResult : `${field} is invalid`);
903
+ isValid = false;
904
+ }
905
+ }
906
+ return {
907
+ value: isValid ? typedValue : void 0,
908
+ errors
909
+ };
910
+ }
911
+
912
+ // src/validation/file.ts
913
+ ({
914
+ // Images
915
+ "image/jpeg": [Buffer.from([255, 216, 255])],
916
+ "image/png": [Buffer.from([137, 80, 78, 71])],
917
+ "image/gif": [Buffer.from("GIF87a"), Buffer.from("GIF89a")],
918
+ "image/webp": [Buffer.from("RIFF")],
919
+ // RIFF....WEBP
920
+ "image/bmp": [Buffer.from([66, 77])],
921
+ // text-based, check separately
922
+ // Documents
923
+ "application/pdf": [Buffer.from("%PDF")],
924
+ "application/zip": [Buffer.from([80, 75, 3, 4])],
925
+ // Audio/Video
926
+ "audio/mpeg": [Buffer.from([255, 251]), Buffer.from([255, 243]), Buffer.from([73, 68, 51])]});
927
+
928
+ // src/logging/redactor.ts
929
+ function createSafeLogger(options = {}) {
930
+ const {
931
+ redactKeys = [],
932
+ maxLength = REDACTION.DEFAULT_MAX_LENGTH,
933
+ redactPatterns = []
934
+ } = options;
935
+ const allRedactKeys = /* @__PURE__ */ new Set([
936
+ ...Array.from(REDACTION.SENSITIVE_KEYS),
937
+ ...redactKeys.map((k) => k.toLowerCase())
938
+ ]);
939
+ function redact(obj, depth = 0) {
940
+ if (depth > INPUT.MAX_RECURSION_DEPTH) return REDACTION.MAX_DEPTH;
941
+ if (obj === null || obj === void 0) return obj;
942
+ if (typeof obj === "string") {
943
+ return redactString(obj, maxLength, redactPatterns);
944
+ }
945
+ if (typeof obj !== "object") return obj;
946
+ if (Array.isArray(obj)) {
947
+ return obj.map((item) => redact(item, depth + 1));
948
+ }
949
+ const result = {};
950
+ for (const [key, value] of Object.entries(obj)) {
951
+ if (allRedactKeys.has(key.toLowerCase())) {
952
+ result[key] = REDACTION.REPLACEMENT;
953
+ } else {
954
+ result[key] = redact(value, depth + 1);
955
+ }
956
+ }
957
+ return result;
958
+ }
959
+ function log(level, message, data) {
960
+ const entry = {
961
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
962
+ level,
963
+ message: redactString(message, maxLength, redactPatterns)
964
+ };
965
+ if (data !== void 0) {
966
+ entry.data = redact(data);
967
+ }
968
+ console.log(JSON.stringify(entry));
969
+ }
970
+ return {
971
+ log,
972
+ info: (msg, data) => log("info", msg, data),
973
+ warn: (msg, data) => log("warn", msg, data),
974
+ error: (msg, data) => log("error", msg, data),
975
+ debug: (msg, data) => log("debug", msg, data)
976
+ };
977
+ }
978
+ function redactString(str, maxLength, patterns) {
979
+ let safe = str.replace(/[\r\n\t]/g, " ").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/g, "");
980
+ for (const pattern of patterns) {
981
+ safe = safe.replace(pattern, REDACTION.REPLACEMENT);
982
+ }
983
+ if (safe.length > maxLength) {
984
+ safe = safe.substring(0, maxLength) + `...${REDACTION.TRUNCATED}`;
985
+ }
986
+ return safe;
987
+ }
988
+
989
+ // src/middleware/main.ts
990
+ function arcis(options = {}) {
991
+ const middlewares = [];
992
+ const cleanupFns = [];
993
+ if (options.headers !== false) {
994
+ const headerOpts = typeof options.headers === "object" ? options.headers : {};
995
+ middlewares.push(createHeaders(headerOpts));
996
+ }
997
+ if (options.rateLimit !== false) {
998
+ const rateLimitOpts = typeof options.rateLimit === "object" ? options.rateLimit : {};
999
+ const rateLimiter = createRateLimiter(rateLimitOpts);
1000
+ middlewares.push(rateLimiter);
1001
+ cleanupFns.push(() => rateLimiter.close());
1002
+ }
1003
+ if (options.sanitize !== false) {
1004
+ const sanitizeOpts = typeof options.sanitize === "object" ? options.sanitize : {};
1005
+ middlewares.push(createSanitizer(sanitizeOpts));
1006
+ }
1007
+ const result = middlewares;
1008
+ result.close = () => {
1009
+ for (const fn of cleanupFns) {
1010
+ fn();
1011
+ }
1012
+ };
1013
+ return result;
1014
+ }
1015
+ var arcisWithMethods = arcis;
1016
+ arcisWithMethods.sanitize = createSanitizer;
1017
+ arcisWithMethods.rateLimit = createRateLimiter;
1018
+ arcisWithMethods.headers = createHeaders;
1019
+ arcisWithMethods.validate = validate;
1020
+ arcisWithMethods.logger = createSafeLogger;
1021
+ arcisWithMethods.errorHandler = createErrorHandler;
1022
+ var main_default = arcisWithMethods;
1023
+
1024
+ // src/middleware/cors.ts
1025
+ var DEFAULT_METHODS = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"];
1026
+ var DEFAULT_HEADERS = ["Content-Type", "Authorization"];
1027
+ var DEFAULT_MAX_AGE = 600;
1028
+ function isOriginAllowed(requestOrigin, allowed) {
1029
+ if (requestOrigin === "null") return false;
1030
+ if (allowed === true) return true;
1031
+ if (typeof allowed === "string") {
1032
+ return requestOrigin === allowed;
1033
+ }
1034
+ if (Array.isArray(allowed)) {
1035
+ return allowed.includes(requestOrigin);
1036
+ }
1037
+ if (allowed instanceof RegExp) {
1038
+ return allowed.test(requestOrigin);
1039
+ }
1040
+ if (typeof allowed === "function") {
1041
+ return allowed(requestOrigin);
1042
+ }
1043
+ return false;
1044
+ }
1045
+ function safeCors(options) {
1046
+ const {
1047
+ origin,
1048
+ methods = DEFAULT_METHODS,
1049
+ allowedHeaders = DEFAULT_HEADERS,
1050
+ exposedHeaders = [],
1051
+ credentials = false,
1052
+ maxAge = DEFAULT_MAX_AGE,
1053
+ preflightContinue = true
1054
+ } = options;
1055
+ return (req, res, next) => {
1056
+ const requestOrigin = req.headers.origin;
1057
+ res.setHeader("Vary", "Origin");
1058
+ if (!requestOrigin) {
1059
+ return next();
1060
+ }
1061
+ const allowed = isOriginAllowed(requestOrigin, origin);
1062
+ if (!allowed) {
1063
+ return next();
1064
+ }
1065
+ res.setHeader("Access-Control-Allow-Origin", requestOrigin);
1066
+ if (credentials) {
1067
+ res.setHeader("Access-Control-Allow-Credentials", "true");
1068
+ }
1069
+ if (exposedHeaders.length > 0) {
1070
+ res.setHeader("Access-Control-Expose-Headers", exposedHeaders.join(", "));
1071
+ }
1072
+ if (req.method === "OPTIONS") {
1073
+ res.setHeader("Access-Control-Allow-Methods", methods.join(", "));
1074
+ res.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
1075
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
1076
+ if (preflightContinue) {
1077
+ res.status(204).end();
1078
+ return;
1079
+ }
1080
+ }
1081
+ next();
1082
+ };
1083
+ }
1084
+ var createCors = safeCors;
1085
+
1086
+ // src/middleware/cookies.ts
1087
+ var COOKIE_ATTRS = {
1088
+ HTTP_ONLY: "; HttpOnly",
1089
+ SECURE: "; Secure",
1090
+ SAME_SITE_STRICT: "; SameSite=Strict",
1091
+ SAME_SITE_LAX: "; SameSite=Lax",
1092
+ SAME_SITE_NONE: "; SameSite=None"
1093
+ };
1094
+ function enforceSecureCookie(cookieStr, options) {
1095
+ const lower = cookieStr.toLowerCase();
1096
+ let result = cookieStr;
1097
+ if (options.httpOnly && !lower.includes("httponly")) {
1098
+ result += COOKIE_ATTRS.HTTP_ONLY;
1099
+ }
1100
+ if (options.secure && !lower.includes("; secure")) {
1101
+ result += COOKIE_ATTRS.SECURE;
1102
+ }
1103
+ if (options.sameSite !== false && !lower.includes("samesite")) {
1104
+ switch (options.sameSite) {
1105
+ case "Strict":
1106
+ result += COOKIE_ATTRS.SAME_SITE_STRICT;
1107
+ break;
1108
+ case "None":
1109
+ result += COOKIE_ATTRS.SAME_SITE_NONE;
1110
+ if (!result.toLowerCase().includes("; secure")) {
1111
+ result += COOKIE_ATTRS.SECURE;
1112
+ }
1113
+ break;
1114
+ case "Lax":
1115
+ default:
1116
+ result += COOKIE_ATTRS.SAME_SITE_LAX;
1117
+ break;
1118
+ }
1119
+ }
1120
+ if (options.path) {
1121
+ if (lower.includes("path=")) {
1122
+ result = result.replace(/;\s*path=[^;]*/i, `; Path=${options.path}`);
1123
+ } else {
1124
+ result += `; Path=${options.path}`;
1125
+ }
1126
+ }
1127
+ return result;
1128
+ }
1129
+ function secureCookieDefaults(options = {}) {
1130
+ const isProduction = process.env.NODE_ENV === "production";
1131
+ const resolved = {
1132
+ httpOnly: options.httpOnly ?? true,
1133
+ secure: options.secure ?? isProduction,
1134
+ sameSite: options.sameSite ?? "Lax",
1135
+ path: options.path
1136
+ };
1137
+ return (_req, res, next) => {
1138
+ const originalSetHeader = res.setHeader.bind(res);
1139
+ res.setHeader = function patchedSetHeader(name, value) {
1140
+ if (name.toLowerCase() === "set-cookie") {
1141
+ if (Array.isArray(value)) {
1142
+ value = value.map((v) => enforceSecureCookie(String(v), resolved));
1143
+ } else {
1144
+ value = enforceSecureCookie(String(value), resolved);
1145
+ }
1146
+ }
1147
+ return originalSetHeader(name, value);
1148
+ };
1149
+ next();
1150
+ };
1151
+ }
1152
+ var createSecureCookies = secureCookieDefaults;
1153
+
1154
+ export { arcis, arcisWithMethods as arcisFunction, createCors, createErrorHandler, createHeaders, createRateLimiter, createSecureCookies, main_default as default, enforceSecureCookie, errorHandler, rateLimit, safeCors, secureCookieDefaults, securityHeaders };
1155
+ //# sourceMappingURL=index.mjs.map
1156
+ //# sourceMappingURL=index.mjs.map