@arcis/node 1.3.0 → 1.4.2

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 (139) hide show
  1. package/README.md +1 -1
  2. package/dist/core/{index.d.mts → constants.d.ts} +21 -70
  3. package/dist/core/constants.d.ts.map +1 -0
  4. package/dist/core/errors.d.ts +53 -0
  5. package/dist/core/errors.d.ts.map +1 -0
  6. package/dist/core/index.d.ts +6 -168
  7. package/dist/core/index.d.ts.map +1 -0
  8. package/dist/core/index.js +11 -3
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/index.mjs +11 -3
  11. package/dist/core/index.mjs.map +1 -1
  12. package/dist/{types-BOkx5YJc.d.mts → core/types.d.ts} +27 -30
  13. package/dist/core/types.d.ts.map +1 -0
  14. package/dist/index.d.ts +71 -166
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +182 -48
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +182 -50
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/logging/index.d.ts +4 -36
  21. package/dist/logging/index.d.ts.map +1 -0
  22. package/dist/logging/index.js.map +1 -1
  23. package/dist/logging/index.mjs.map +1 -1
  24. package/dist/logging/{index.d.mts → redactor.d.ts} +5 -9
  25. package/dist/logging/redactor.d.ts.map +1 -0
  26. package/dist/middleware/bot-detection.d.ts +86 -0
  27. package/dist/middleware/bot-detection.d.ts.map +1 -0
  28. package/dist/middleware/cookies.d.ts +48 -0
  29. package/dist/middleware/cookies.d.ts.map +1 -0
  30. package/dist/middleware/cors.d.ts +65 -0
  31. package/dist/middleware/cors.d.ts.map +1 -0
  32. package/dist/middleware/csrf.d.ts +109 -0
  33. package/dist/middleware/csrf.d.ts.map +1 -0
  34. package/dist/middleware/error-handler.d.ts +43 -0
  35. package/dist/middleware/error-handler.d.ts.map +1 -0
  36. package/dist/middleware/headers.d.ts +29 -0
  37. package/dist/middleware/headers.d.ts.map +1 -0
  38. package/dist/middleware/hpp.d.ts +56 -0
  39. package/dist/middleware/hpp.d.ts.map +1 -0
  40. package/dist/middleware/index.d.ts +16 -3
  41. package/dist/middleware/index.d.ts.map +1 -0
  42. package/dist/middleware/index.js +68 -31
  43. package/dist/middleware/index.js.map +1 -1
  44. package/dist/middleware/index.mjs +69 -32
  45. package/dist/middleware/index.mjs.map +1 -1
  46. package/dist/middleware/main.d.ts +40 -0
  47. package/dist/middleware/main.d.ts.map +1 -0
  48. package/dist/middleware/rate-limit-sliding.d.ts +46 -0
  49. package/dist/middleware/rate-limit-sliding.d.ts.map +1 -0
  50. package/dist/middleware/rate-limit-token.d.ts +51 -0
  51. package/dist/middleware/rate-limit-token.d.ts.map +1 -0
  52. package/dist/middleware/rate-limit.d.ts +34 -0
  53. package/dist/middleware/rate-limit.d.ts.map +1 -0
  54. package/dist/sanitizers/command.d.ts +28 -0
  55. package/dist/sanitizers/command.d.ts.map +1 -0
  56. package/dist/sanitizers/encode.d.ts +46 -0
  57. package/dist/sanitizers/encode.d.ts.map +1 -0
  58. package/dist/sanitizers/headers.d.ts +46 -0
  59. package/dist/sanitizers/headers.d.ts.map +1 -0
  60. package/dist/sanitizers/index.d.ts +18 -22
  61. package/dist/sanitizers/index.d.ts.map +1 -0
  62. package/dist/sanitizers/index.js +90 -32
  63. package/dist/sanitizers/index.js.map +1 -1
  64. package/dist/sanitizers/index.mjs +88 -33
  65. package/dist/sanitizers/index.mjs.map +1 -1
  66. package/dist/sanitizers/jsonp.d.ts +34 -0
  67. package/dist/sanitizers/jsonp.d.ts.map +1 -0
  68. package/dist/sanitizers/ldap.d.ts +42 -0
  69. package/dist/sanitizers/ldap.d.ts.map +1 -0
  70. package/dist/sanitizers/nosql.d.ts +31 -0
  71. package/dist/sanitizers/nosql.d.ts.map +1 -0
  72. package/dist/sanitizers/path.d.ts +28 -0
  73. package/dist/sanitizers/path.d.ts.map +1 -0
  74. package/dist/sanitizers/pii.d.ts +80 -0
  75. package/dist/sanitizers/pii.d.ts.map +1 -0
  76. package/dist/sanitizers/prototype.d.ts +34 -0
  77. package/dist/sanitizers/prototype.d.ts.map +1 -0
  78. package/dist/sanitizers/sanitize.d.ts +51 -0
  79. package/dist/sanitizers/sanitize.d.ts.map +1 -0
  80. package/dist/sanitizers/sql.d.ts +28 -0
  81. package/dist/sanitizers/sql.d.ts.map +1 -0
  82. package/dist/sanitizers/ssti.d.ts +20 -0
  83. package/dist/sanitizers/ssti.d.ts.map +1 -0
  84. package/dist/sanitizers/utils.d.ts +19 -0
  85. package/dist/sanitizers/utils.d.ts.map +1 -0
  86. package/dist/sanitizers/xss.d.ts +35 -0
  87. package/dist/sanitizers/xss.d.ts.map +1 -0
  88. package/dist/sanitizers/xxe.d.ts +20 -0
  89. package/dist/sanitizers/xxe.d.ts.map +1 -0
  90. package/dist/stores/index.d.ts +6 -104
  91. package/dist/stores/index.d.ts.map +1 -0
  92. package/dist/stores/index.js +21 -1
  93. package/dist/stores/index.js.map +1 -1
  94. package/dist/stores/index.mjs +21 -1
  95. package/dist/stores/index.mjs.map +1 -1
  96. package/dist/stores/memory.d.ts +29 -0
  97. package/dist/stores/memory.d.ts.map +1 -0
  98. package/dist/stores/{index.d.mts → redis.d.ts} +6 -45
  99. package/dist/stores/redis.d.ts.map +1 -0
  100. package/dist/utils/duration.d.ts +34 -0
  101. package/dist/utils/duration.d.ts.map +1 -0
  102. package/dist/utils/fingerprint.d.ts +64 -0
  103. package/dist/utils/fingerprint.d.ts.map +1 -0
  104. package/dist/utils/index.d.ts +10 -0
  105. package/dist/utils/index.d.ts.map +1 -0
  106. package/dist/utils/index.js +188 -0
  107. package/dist/utils/index.js.map +1 -0
  108. package/dist/utils/index.mjs +182 -0
  109. package/dist/utils/index.mjs.map +1 -0
  110. package/dist/utils/ip.d.ts +70 -0
  111. package/dist/utils/ip.d.ts.map +1 -0
  112. package/dist/validation/email.d.ts +82 -0
  113. package/dist/validation/email.d.ts.map +1 -0
  114. package/dist/validation/file.d.ts +90 -0
  115. package/dist/validation/file.d.ts.map +1 -0
  116. package/dist/validation/index.d.ts +10 -3
  117. package/dist/validation/index.d.ts.map +1 -0
  118. package/dist/validation/index.js +38 -21
  119. package/dist/validation/index.js.map +1 -1
  120. package/dist/validation/index.mjs +38 -21
  121. package/dist/validation/index.mjs.map +1 -1
  122. package/dist/validation/redirect.d.ts +64 -0
  123. package/dist/validation/redirect.d.ts.map +1 -0
  124. package/dist/validation/schema.d.ts +36 -0
  125. package/dist/validation/schema.d.ts.map +1 -0
  126. package/dist/validation/url.d.ts +65 -0
  127. package/dist/validation/url.d.ts.map +1 -0
  128. package/package.json +8 -6
  129. package/dist/encode-CrQCGlBq.d.mts +0 -484
  130. package/dist/encode-jl9sOwmA.d.ts +0 -484
  131. package/dist/index-BAhgn9V2.d.ts +0 -532
  132. package/dist/index-BGNKspqH.d.ts +0 -340
  133. package/dist/index-Cd02z-0j.d.mts +0 -340
  134. package/dist/index-DgJtWMSj.d.mts +0 -532
  135. package/dist/index.d.mts +0 -175
  136. package/dist/middleware/index.d.mts +0 -3
  137. package/dist/sanitizers/index.d.mts +0 -24
  138. package/dist/types-BOkx5YJc.d.ts +0 -279
  139. package/dist/validation/index.d.mts +0 -3
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @module @arcis/node/validation/file
3
+ * File upload validation and filename sanitization
4
+ */
5
+ /** File upload validation options */
6
+ export interface ValidateFileOptions {
7
+ /** Maximum file size in bytes. Default: 5MB */
8
+ maxSize?: number;
9
+ /** Allowed MIME types (e.g., ['image/jpeg', 'image/png']) */
10
+ allowedTypes?: string[];
11
+ /** Allowed file extensions (e.g., ['.jpg', '.png']). Includes dot. */
12
+ allowedExtensions?: string[];
13
+ /** Block dangerous/executable extensions. Default: true */
14
+ blockExecutables?: boolean;
15
+ /** Validate magic bytes match the claimed MIME type. Default: true */
16
+ validateMagicBytes?: boolean;
17
+ /** Block files with no extension. Default: true */
18
+ blockNoExtension?: boolean;
19
+ /** Block double extensions (e.g., file.php.jpg). Default: true */
20
+ blockDoubleExtensions?: boolean;
21
+ }
22
+ /** File metadata for validation */
23
+ export interface FileInput {
24
+ /** Original filename */
25
+ filename: string;
26
+ /** MIME type (as claimed by client) */
27
+ mimetype: string;
28
+ /** File size in bytes */
29
+ size: number;
30
+ /** File content buffer (for magic byte validation) */
31
+ buffer?: Buffer;
32
+ }
33
+ /** File validation result */
34
+ export interface ValidateFileResult {
35
+ /** Whether the file passed validation */
36
+ valid: boolean;
37
+ /** Validation errors (empty if valid) */
38
+ errors: string[];
39
+ /** Sanitized filename (safe for storage) */
40
+ sanitizedFilename: string;
41
+ }
42
+ /**
43
+ * Sanitize a filename for safe storage.
44
+ *
45
+ * Strips path traversal, null bytes, control characters, and special characters.
46
+ * Preserves the extension and converts to a filesystem-safe name.
47
+ *
48
+ * @param filename - The original filename
49
+ * @returns A sanitized filename safe for storage
50
+ *
51
+ * @example
52
+ * sanitizeFilename('../../etc/passwd') // 'etc_passwd'
53
+ * sanitizeFilename('file<name>.jpg') // 'filename.jpg'
54
+ * sanitizeFilename('photo (1).jpg') // 'photo_1.jpg'
55
+ * sanitizeFilename('.htaccess') // 'htaccess'
56
+ */
57
+ export declare function sanitizeFilename(filename: string): string;
58
+ /**
59
+ * Validate a file upload for security.
60
+ *
61
+ * Checks file size, MIME type, extension, magic bytes, and dangerous patterns.
62
+ * Returns a result with validation errors and a sanitized filename.
63
+ *
64
+ * @param file - File metadata and optional content
65
+ * @param options - Validation options
66
+ * @returns Validation result
67
+ *
68
+ * @example
69
+ * const result = validateFile(
70
+ * { filename: 'photo.jpg', mimetype: 'image/jpeg', size: 1024, buffer },
71
+ * { allowedTypes: ['image/jpeg', 'image/png'], maxSize: 2 * 1024 * 1024 }
72
+ * );
73
+ * if (!result.valid) {
74
+ * return res.status(400).json({ errors: result.errors });
75
+ * }
76
+ * // Use result.sanitizedFilename for storage
77
+ *
78
+ * @example
79
+ * // Block executables only (no whitelist)
80
+ * const result = validateFile(file, { blockExecutables: true });
81
+ */
82
+ export declare function validateFile(file: FileInput, options?: ValidateFileOptions): ValidateFileResult;
83
+ /**
84
+ * Check if a file extension is considered dangerous/executable.
85
+ *
86
+ * @param filename - Filename or extension to check
87
+ * @returns true if the extension is dangerous
88
+ */
89
+ export declare function isDangerousExtension(filename: string): boolean;
90
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/validation/file.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAuDH,qCAAqC;AACrC,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kEAAkE;IAClE,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6BAA6B;AAC7B,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,yCAAyC;IACzC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,4CAA4C;IAC5C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAYD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqCzD;AAmDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,EACf,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CA6DpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG9D"}
@@ -1,3 +1,10 @@
1
- export { g as createValidator, i as isDangerousExtension, h as isRedirectSafe, j as isUrlSafe, k as isValidEmailSyntax, s as sanitizeFilename, v as validate, l as validateEmail, m as validateFile, n as validateRedirect, o as validateUrl, p as verifyEmailMx } from '../index-BGNKspqH.js';
2
- import 'express';
3
- import '../types-BOkx5YJc.js';
1
+ /**
2
+ * @module @arcis/node/validation
3
+ * Request validation for Arcis
4
+ */
5
+ export { validate, createValidator } from './schema';
6
+ export { validateFile, sanitizeFilename, isDangerousExtension } from './file';
7
+ export { validateUrl, isUrlSafe } from './url';
8
+ export { validateRedirect, isRedirectSafe } from './redirect';
9
+ export { validateEmail, verifyEmailMx, isValidEmailSyntax } from './email';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
@@ -31,7 +31,15 @@ var XSS_REMOVE_PATTERNS = [
31
31
  /javascript\s*:/gi,
32
32
  /vbscript\s*:/gi,
33
33
  /** data: URIs with HTML/script content */
34
- /data\s*:\s*text\/html[^>\s]*/gi
34
+ /data\s*:\s*text\/html[^>\s]*/gi,
35
+ /** form tag injection — phishing via action= redirection */
36
+ /<form[\s>][^>]*/gi,
37
+ /** meta tag injection — http-equiv refresh or CSP bypass */
38
+ /<meta[\s>][^>]*/gi,
39
+ /** base href hijacking */
40
+ /<base[\s>][^>]*/gi,
41
+ /** link tag injection — stylesheet or preload attacks */
42
+ /<link[\s>][^>]*/gi
35
43
  ];
36
44
  var SQL_PATTERNS = [
37
45
  /** SQL keywords */
@@ -95,8 +103,8 @@ var COMMAND_PATTERNS = [
95
103
  /[;&|`]/g,
96
104
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
97
105
  /\$\(/g,
98
- /** URL-encoded newline/carriage-return injection (%0a, %0d) */
99
- /%0[ad]/gi
106
+ /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
107
+ /%0[0-9a-f]/gi
100
108
  ];
101
109
  var VALIDATION = {
102
110
  /**
@@ -261,26 +269,31 @@ function sanitizePath(input, collectThreats = false) {
261
269
  const threats = [];
262
270
  let value = input;
263
271
  let wasSanitized = false;
264
- for (const pattern of PATH_PATTERNS) {
265
- pattern.lastIndex = 0;
266
- if (pattern.test(value)) {
272
+ value = value.normalize("NFKC");
273
+ let prev;
274
+ do {
275
+ prev = value;
276
+ for (const pattern of PATH_PATTERNS) {
267
277
  pattern.lastIndex = 0;
268
- if (collectThreats) {
269
- const matches = value.match(pattern);
270
- if (matches) {
271
- for (const match of matches) {
272
- threats.push({
273
- type: "path_traversal",
274
- pattern: pattern.source,
275
- original: match
276
- });
278
+ if (pattern.test(value)) {
279
+ pattern.lastIndex = 0;
280
+ if (collectThreats) {
281
+ const matches = value.match(pattern);
282
+ if (matches) {
283
+ for (const match of matches) {
284
+ threats.push({
285
+ type: "path_traversal",
286
+ pattern: pattern.source,
287
+ original: match
288
+ });
289
+ }
277
290
  }
278
291
  }
292
+ value = value.replace(pattern, "");
293
+ wasSanitized = true;
279
294
  }
280
- value = value.replace(pattern, "");
281
- wasSanitized = true;
282
295
  }
283
- }
296
+ } while (value !== prev);
284
297
  if (collectThreats) {
285
298
  return { value, wasSanitized, threats };
286
299
  }
@@ -338,7 +351,7 @@ function sanitizeString(value, options = {}) {
338
351
  if (value.length > maxSize) {
339
352
  throw new InputTooLargeError(maxSize, value.length);
340
353
  }
341
- const reject = options.mode !== "sanitize";
354
+ const reject = options.mode === "reject";
342
355
  let result = value;
343
356
  if (options.sql !== false) {
344
357
  if (reject) {
@@ -793,8 +806,12 @@ function checkPrivateIp(hostname) {
793
806
  if (hostname === "metadata.google.internal" || hostname === "metadata.internal" || hostname === "metadata.azure.internal") {
794
807
  return "cloud metadata endpoint";
795
808
  }
796
- const ipv6 = hostname.replace(/^\[|\]$/g, "");
797
- if (ipv6 === "::1" || ipv6 === "::" || ipv6.startsWith("fc") || ipv6.startsWith("fd") || ipv6.startsWith("fe80")) {
809
+ let ipv6 = hostname.replace(/^\[|\]$/g, "");
810
+ const zoneIdx = ipv6.indexOf("%");
811
+ if (zoneIdx !== -1) {
812
+ ipv6 = ipv6.slice(0, zoneIdx);
813
+ }
814
+ if (ipv6 === "::1" || ipv6 === "::" || /^fc[0-9a-f]{2}:/i.test(ipv6) || /^fd[0-9a-f]{2}:/i.test(ipv6) || /^fe80:/i.test(ipv6) || /^ff[0-9a-f]{2}:/i.test(ipv6)) {
798
815
  return "private IPv6 address";
799
816
  }
800
817
  const mappedDotted = ipv6.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);