verification-layer 0.20.0 → 0.22.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 (180) hide show
  1. package/README.md +251 -615
  2. package/dist/cli.js +542 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/marketplace/index.d.ts +8 -0
  5. package/dist/marketplace/index.d.ts.map +1 -0
  6. package/dist/marketplace/index.js +7 -0
  7. package/dist/marketplace/index.js.map +1 -0
  8. package/dist/marketplace/installer.d.ts +62 -0
  9. package/dist/marketplace/installer.d.ts.map +1 -0
  10. package/dist/marketplace/installer.js +254 -0
  11. package/dist/marketplace/installer.js.map +1 -0
  12. package/dist/marketplace/registry.d.ts +52 -0
  13. package/dist/marketplace/registry.d.ts.map +1 -0
  14. package/dist/marketplace/registry.js +759 -0
  15. package/dist/marketplace/registry.js.map +1 -0
  16. package/dist/marketplace/types.d.ts +123 -0
  17. package/dist/marketplace/types.d.ts.map +1 -0
  18. package/dist/marketplace/types.js +6 -0
  19. package/dist/marketplace/types.js.map +1 -0
  20. package/dist/reporters/audit-report.d.ts.map +1 -1
  21. package/dist/reporters/audit-report.js +180 -0
  22. package/dist/reporters/audit-report.js.map +1 -1
  23. package/dist/reporters/index.d.ts.map +1 -1
  24. package/dist/reporters/index.js +2612 -5
  25. package/dist/reporters/index.js.map +1 -1
  26. package/dist/scan.d.ts.map +1 -1
  27. package/dist/scan.js +15 -1
  28. package/dist/scan.js.map +1 -1
  29. package/dist/scanners/api-security/index.d.ts +7 -0
  30. package/dist/scanners/api-security/index.d.ts.map +1 -0
  31. package/dist/scanners/api-security/index.js +139 -0
  32. package/dist/scanners/api-security/index.js.map +1 -0
  33. package/dist/scanners/api-security/index.test.d.ts +5 -0
  34. package/dist/scanners/api-security/index.test.d.ts.map +1 -0
  35. package/dist/scanners/api-security/index.test.js +360 -0
  36. package/dist/scanners/api-security/index.test.js.map +1 -0
  37. package/dist/scanners/api-security/patterns.d.ts +32 -0
  38. package/dist/scanners/api-security/patterns.d.ts.map +1 -0
  39. package/dist/scanners/api-security/patterns.js +159 -0
  40. package/dist/scanners/api-security/patterns.js.map +1 -0
  41. package/dist/scanners/authentication/index.d.ts +7 -0
  42. package/dist/scanners/authentication/index.d.ts.map +1 -0
  43. package/dist/scanners/authentication/index.js +107 -0
  44. package/dist/scanners/authentication/index.js.map +1 -0
  45. package/dist/scanners/authentication/index.test.d.ts +5 -0
  46. package/dist/scanners/authentication/index.test.d.ts.map +1 -0
  47. package/dist/scanners/authentication/index.test.js +379 -0
  48. package/dist/scanners/authentication/index.test.js.map +1 -0
  49. package/dist/scanners/authentication/patterns.d.ts +32 -0
  50. package/dist/scanners/authentication/patterns.d.ts.map +1 -0
  51. package/dist/scanners/authentication/patterns.js +133 -0
  52. package/dist/scanners/authentication/patterns.js.map +1 -0
  53. package/dist/scanners/configuration/index.d.ts +8 -0
  54. package/dist/scanners/configuration/index.d.ts.map +1 -0
  55. package/dist/scanners/configuration/index.js +87 -0
  56. package/dist/scanners/configuration/index.js.map +1 -0
  57. package/dist/scanners/configuration/index.test.d.ts +5 -0
  58. package/dist/scanners/configuration/index.test.d.ts.map +1 -0
  59. package/dist/scanners/configuration/index.test.js +344 -0
  60. package/dist/scanners/configuration/index.test.js.map +1 -0
  61. package/dist/scanners/configuration/patterns.d.ts +32 -0
  62. package/dist/scanners/configuration/patterns.d.ts.map +1 -0
  63. package/dist/scanners/configuration/patterns.js +146 -0
  64. package/dist/scanners/configuration/patterns.js.map +1 -0
  65. package/dist/scanners/credentials/index.d.ts +7 -0
  66. package/dist/scanners/credentials/index.d.ts.map +1 -0
  67. package/dist/scanners/credentials/index.js +129 -0
  68. package/dist/scanners/credentials/index.js.map +1 -0
  69. package/dist/scanners/credentials/index.test.d.ts +5 -0
  70. package/dist/scanners/credentials/index.test.d.ts.map +1 -0
  71. package/dist/scanners/credentials/index.test.js +395 -0
  72. package/dist/scanners/credentials/index.test.js.map +1 -0
  73. package/dist/scanners/credentials/patterns.d.ts +32 -0
  74. package/dist/scanners/credentials/patterns.d.ts.map +1 -0
  75. package/dist/scanners/credentials/patterns.js +140 -0
  76. package/dist/scanners/credentials/patterns.js.map +1 -0
  77. package/dist/scanners/errors/index.d.ts +8 -0
  78. package/dist/scanners/errors/index.d.ts.map +1 -0
  79. package/dist/scanners/errors/index.js +78 -0
  80. package/dist/scanners/errors/index.js.map +1 -0
  81. package/dist/scanners/errors/index.test.d.ts +5 -0
  82. package/dist/scanners/errors/index.test.d.ts.map +1 -0
  83. package/dist/scanners/errors/index.test.js +330 -0
  84. package/dist/scanners/errors/index.test.js.map +1 -0
  85. package/dist/scanners/errors/patterns.d.ts +27 -0
  86. package/dist/scanners/errors/patterns.d.ts.map +1 -0
  87. package/dist/scanners/errors/patterns.js +97 -0
  88. package/dist/scanners/errors/patterns.js.map +1 -0
  89. package/dist/scanners/hipaa2026/index.d.ts +8 -0
  90. package/dist/scanners/hipaa2026/index.d.ts.map +1 -0
  91. package/dist/scanners/hipaa2026/index.js +345 -0
  92. package/dist/scanners/hipaa2026/index.js.map +1 -0
  93. package/dist/scanners/hipaa2026/index.test.d.ts +5 -0
  94. package/dist/scanners/hipaa2026/index.test.d.ts.map +1 -0
  95. package/dist/scanners/hipaa2026/index.test.js +332 -0
  96. package/dist/scanners/hipaa2026/index.test.js.map +1 -0
  97. package/dist/scanners/hipaa2026/patterns.d.ts +57 -0
  98. package/dist/scanners/hipaa2026/patterns.d.ts.map +1 -0
  99. package/dist/scanners/hipaa2026/patterns.js +268 -0
  100. package/dist/scanners/hipaa2026/patterns.js.map +1 -0
  101. package/dist/scanners/operational/index.d.ts +7 -0
  102. package/dist/scanners/operational/index.d.ts.map +1 -0
  103. package/dist/scanners/operational/index.js +171 -0
  104. package/dist/scanners/operational/index.js.map +1 -0
  105. package/dist/scanners/operational/index.test.d.ts +5 -0
  106. package/dist/scanners/operational/index.test.d.ts.map +1 -0
  107. package/dist/scanners/operational/index.test.js +406 -0
  108. package/dist/scanners/operational/index.test.js.map +1 -0
  109. package/dist/scanners/operational/patterns.d.ts +33 -0
  110. package/dist/scanners/operational/patterns.d.ts.map +1 -0
  111. package/dist/scanners/operational/patterns.js +151 -0
  112. package/dist/scanners/operational/patterns.js.map +1 -0
  113. package/dist/scanners/rbac/index.d.ts +7 -0
  114. package/dist/scanners/rbac/index.d.ts.map +1 -0
  115. package/dist/scanners/rbac/index.js +145 -0
  116. package/dist/scanners/rbac/index.js.map +1 -0
  117. package/dist/scanners/rbac/index.test.d.ts +5 -0
  118. package/dist/scanners/rbac/index.test.d.ts.map +1 -0
  119. package/dist/scanners/rbac/index.test.js +422 -0
  120. package/dist/scanners/rbac/index.test.js.map +1 -0
  121. package/dist/scanners/rbac/patterns.d.ts +32 -0
  122. package/dist/scanners/rbac/patterns.d.ts.map +1 -0
  123. package/dist/scanners/rbac/patterns.js +124 -0
  124. package/dist/scanners/rbac/patterns.js.map +1 -0
  125. package/dist/scanners/revocation/index.d.ts +8 -0
  126. package/dist/scanners/revocation/index.d.ts.map +1 -0
  127. package/dist/scanners/revocation/index.js +83 -0
  128. package/dist/scanners/revocation/index.js.map +1 -0
  129. package/dist/scanners/revocation/index.test.d.ts +5 -0
  130. package/dist/scanners/revocation/index.test.d.ts.map +1 -0
  131. package/dist/scanners/revocation/index.test.js +332 -0
  132. package/dist/scanners/revocation/index.test.js.map +1 -0
  133. package/dist/scanners/revocation/patterns.d.ts +27 -0
  134. package/dist/scanners/revocation/patterns.d.ts.map +1 -0
  135. package/dist/scanners/revocation/patterns.js +109 -0
  136. package/dist/scanners/revocation/patterns.js.map +1 -0
  137. package/dist/scanners/sanitization/index.d.ts +8 -0
  138. package/dist/scanners/sanitization/index.d.ts.map +1 -0
  139. package/dist/scanners/sanitization/index.js +98 -0
  140. package/dist/scanners/sanitization/index.js.map +1 -0
  141. package/dist/scanners/sanitization/index.test.d.ts +5 -0
  142. package/dist/scanners/sanitization/index.test.d.ts.map +1 -0
  143. package/dist/scanners/sanitization/index.test.js +370 -0
  144. package/dist/scanners/sanitization/index.test.js.map +1 -0
  145. package/dist/scanners/sanitization/patterns.d.ts +27 -0
  146. package/dist/scanners/sanitization/patterns.d.ts.map +1 -0
  147. package/dist/scanners/sanitization/patterns.js +117 -0
  148. package/dist/scanners/sanitization/patterns.js.map +1 -0
  149. package/dist/training/certificate.d.ts +26 -0
  150. package/dist/training/certificate.d.ts.map +1 -0
  151. package/dist/training/certificate.js +92 -0
  152. package/dist/training/certificate.js.map +1 -0
  153. package/dist/training/index.d.ts +3 -0
  154. package/dist/training/index.d.ts.map +1 -0
  155. package/dist/training/index.js +243 -0
  156. package/dist/training/index.js.map +1 -0
  157. package/dist/training/modules.d.ts +13 -0
  158. package/dist/training/modules.d.ts.map +1 -0
  159. package/dist/training/modules.js +608 -0
  160. package/dist/training/modules.js.map +1 -0
  161. package/dist/training/questions.d.ts +9 -0
  162. package/dist/training/questions.d.ts.map +1 -0
  163. package/dist/training/questions.js +505 -0
  164. package/dist/training/questions.js.map +1 -0
  165. package/dist/types.d.ts +45 -0
  166. package/dist/types.d.ts.map +1 -1
  167. package/dist/utils/npm-audit.d.ts +6 -0
  168. package/dist/utils/npm-audit.d.ts.map +1 -0
  169. package/dist/utils/npm-audit.js +95 -0
  170. package/dist/utils/npm-audit.js.map +1 -0
  171. package/dist/utils/scan-history.d.ts +59 -0
  172. package/dist/utils/scan-history.d.ts.map +1 -0
  173. package/dist/utils/scan-history.js +170 -0
  174. package/dist/utils/scan-history.js.map +1 -0
  175. package/package.json +4 -1
  176. package/templates/baa-verification-letter.md +105 -0
  177. package/templates/irp.md +545 -0
  178. package/templates/notice-of-privacy-practices.md +491 -0
  179. package/templates/physical-safeguards-checklist.md +247 -0
  180. package/templates/security-officer-designation.md +237 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Token Revocation Security Detection Patterns
3
+ * Detects JWT usage without revocation mechanisms and excessive token expiration
4
+ */
5
+ /**
6
+ * REVOKE-001: JWT Without Server-Side Revocation Mechanism
7
+ * Detects JWT usage without revocation support
8
+ */
9
+ export const JWT_WITHOUT_REVOCATION = {
10
+ id: 'REVOKE-001',
11
+ name: 'JWT Without Server-Side Revocation Mechanism',
12
+ description: 'JWT token generation (jwt.sign, jsonwebtoken, jose) without server-side revocation mechanism (revoke, blacklist, allowlist, tokenStore, invalidate, denylist). HIPAA NPRM requires ability to revoke access within 1 hour.',
13
+ severity: 'high',
14
+ hipaaReference: 'NPRM §164.308(a)(3)(ii)(C) - Access Revocation',
15
+ patterns: [
16
+ // jwt.sign() calls
17
+ /jwt\.sign\s*\(/i,
18
+ // jsonwebtoken.sign() calls
19
+ /jsonwebtoken\.sign\s*\(/i,
20
+ // jose sign methods
21
+ /new\s+(?:SignJWT|CompactSign)\s*\(/i,
22
+ /jose\.(?:SignJWT|sign)/i,
23
+ // JWT token generation
24
+ /(?:create|generate)(?:Access)?Token\s*\([^)]*\)/i,
25
+ // Sign JWT patterns
26
+ /\.sign\s*\([^)]*jwt/i,
27
+ ],
28
+ negativePatterns: [
29
+ // Revocation mechanisms
30
+ /revoke/i,
31
+ /blacklist/i,
32
+ /allowlist/i,
33
+ /whitelist/i,
34
+ /denylist/i,
35
+ /tokenStore/i,
36
+ /invalidate/i,
37
+ /revokedTokens/i,
38
+ /tokenBlacklist/i,
39
+ /redis/i, // Common for token storage
40
+ /session/i, // Session-based auth has built-in revocation
41
+ // Database token storage
42
+ /tokenRepository/i,
43
+ /saveToken/i,
44
+ /storeToken/i,
45
+ // Revocation libraries
46
+ /express-jwt-blacklist/i,
47
+ /jwt-redis/i,
48
+ // Comments indicating revocation is handled elsewhere
49
+ /revocation.*handled/i,
50
+ /revoke.*separate/i,
51
+ ],
52
+ recommendation: 'Implement server-side token revocation mechanism. Store active tokens in Redis/database with TTL, or maintain a blacklist of revoked tokens. Example: await redis.set(`token:${userId}`, token, "EX", 3600); await redis.del(`token:${userId}`) to revoke. HIPAA requires ability to revoke access within 1 hour.',
53
+ category: 'access-control',
54
+ };
55
+ /**
56
+ * REVOKE-002: Excessive Token Expiration Time
57
+ * Detects tokens with expirations longer than recommended
58
+ */
59
+ export const EXCESSIVE_TOKEN_EXPIRATION = {
60
+ id: 'REVOKE-002',
61
+ name: 'Excessive Token Expiration Time',
62
+ description: 'JWT token with excessive expiration time. Access tokens should expire within 24 hours (expiresIn: "24h" or less). Refresh tokens should not exceed 7 days for sensitive data.',
63
+ severity: 'medium',
64
+ hipaaReference: 'NPRM §164.308(a)(3)(ii)(C) - Access Revocation',
65
+ patterns: [
66
+ // expiresIn with excessive time
67
+ // Days: 2d, 3d, 7d, 30d, 90d, etc. (more than 1 day)
68
+ /expiresIn\s*:\s*['"`](?:[2-9]|[1-9]\d+)d['"`]/i,
69
+ // Hours: 25h, 48h, 72h, etc. (more than 24 hours)
70
+ /expiresIn\s*:\s*['"`](?:2[5-9]|[3-9]\d|\d{3,})h['"`]/i,
71
+ // Weeks, years (w, y - note: m in JWT means minutes, not months)
72
+ /expiresIn\s*:\s*['"`]\d+[wy]['"`]/i,
73
+ // Months (spelled out)
74
+ /expiresIn\s*:\s*['"`]\d+\s*(?:month|months|mo)['"`]/i,
75
+ // Numeric seconds: more than 86400 (24 hours)
76
+ /expiresIn\s*:\s*(?:8[7-9]\d{3}|\d{6,})\b/i,
77
+ // maxAge with excessive time (more than 7 days = 604800000 ms)
78
+ /maxAge\s*:\s*(?:6[1-9]\d{7}|[7-9]\d{8}|\d{9,})\b/i,
79
+ // exp claim with far future timestamps (common mistake)
80
+ /exp\s*:\s*Math\.floor\s*\([^)]*\+\s*(?:2[5-9]|[3-9]\d|\d{3,})\s*\*\s*(?:60\s*\*\s*60|3600)/i,
81
+ ],
82
+ negativePatterns: [
83
+ // Acceptable expirations for access tokens (1h - 24h)
84
+ /expiresIn\s*:\s*['"`](?:1?\d|2[0-4])h['"`]/i,
85
+ /expiresIn\s*:\s*['"`]1d['"`]/i,
86
+ // Minutes (m in JWT means minutes, not months)
87
+ /expiresIn\s*:\s*['"`]\d+m['"`]/i,
88
+ // Seconds (s)
89
+ /expiresIn\s*:\s*['"`]\d+s['"`]/i,
90
+ // Acceptable numeric values (up to 24 hours = 86400 seconds)
91
+ /expiresIn\s*:\s*(?:[1-7]\d{3}|[1-8][0-5]\d{3}|86[0-3]\d{2}|8640{1,2})\b/i,
92
+ // Refresh tokens (explicitly marked as such)
93
+ /refresh.*token/i,
94
+ /refreshToken/i,
95
+ // Remember me tokens (longer expiration is acceptable)
96
+ /remember.*me/i,
97
+ /rememberMe/i,
98
+ // API keys (different from access tokens)
99
+ /api.*key/i,
100
+ /apiKey/i,
101
+ ],
102
+ recommendation: 'Use short-lived access tokens (≤ 24 hours): expiresIn: "1h" or expiresIn: "15m". For longer sessions, implement refresh token rotation. Example: accessToken with expiresIn: "1h", refreshToken with expiresIn: "7d". Reduce attack window if token is compromised.',
103
+ category: 'access-control',
104
+ };
105
+ export const ALL_REVOCATION_PATTERNS = [
106
+ JWT_WITHOUT_REVOCATION,
107
+ EXCESSIVE_TOKEN_EXPIRATION,
108
+ ];
109
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/scanners/revocation/patterns.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,8CAA8C;IACpD,WAAW,EACT,4NAA4N;IAC9N,QAAQ,EAAE,MAAM;IAChB,cAAc,EAAE,gDAAgD;IAChE,QAAQ,EAAE;QACR,mBAAmB;QACnB,iBAAiB;QAEjB,4BAA4B;QAC5B,0BAA0B;QAE1B,oBAAoB;QACpB,qCAAqC;QACrC,yBAAyB;QAEzB,uBAAuB;QACvB,kDAAkD;QAElD,oBAAoB;QACpB,sBAAsB;KACvB;IACD,gBAAgB,EAAE;QAChB,wBAAwB;QACxB,SAAS;QACT,YAAY;QACZ,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,aAAa;QACb,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,QAAQ,EAAE,2BAA2B;QACrC,UAAU,EAAE,6CAA6C;QAEzD,yBAAyB;QACzB,kBAAkB;QAClB,YAAY;QACZ,aAAa;QAEb,uBAAuB;QACvB,wBAAwB;QACxB,YAAY;QAEZ,sDAAsD;QACtD,sBAAsB;QACtB,mBAAmB;KACpB;IACD,cAAc,EACZ,mTAAmT;IACrT,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,iCAAiC;IACvC,WAAW,EACT,+KAA+K;IACjL,QAAQ,EAAE,QAAQ;IAClB,cAAc,EAAE,gDAAgD;IAChE,QAAQ,EAAE;QACR,gCAAgC;QAChC,qDAAqD;QACrD,gDAAgD;QAEhD,kDAAkD;QAClD,uDAAuD;QAEvD,iEAAiE;QACjE,oCAAoC;QAEpC,uBAAuB;QACvB,sDAAsD;QAEtD,8CAA8C;QAC9C,2CAA2C;QAE3C,+DAA+D;QAC/D,mDAAmD;QAEnD,wDAAwD;QACxD,6FAA6F;KAC9F;IACD,gBAAgB,EAAE;QAChB,sDAAsD;QACtD,6CAA6C;QAC7C,+BAA+B;QAE/B,+CAA+C;QAC/C,iCAAiC;QAEjC,cAAc;QACd,iCAAiC;QAEjC,6DAA6D;QAC7D,0EAA0E;QAE1E,6CAA6C;QAC7C,iBAAiB;QACjB,eAAe;QAEf,uDAAuD;QACvD,eAAe;QACf,aAAa;QAEb,0CAA0C;QAC1C,WAAW;QACX,SAAS;KACV;IACD,cAAc,EACZ,qQAAqQ;IACvQ,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAwB;IAC1D,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Input Sanitization Security Scanner
3
+ * Detects unsafe user input handling and file upload configurations
4
+ */
5
+ import type { Scanner } from '../../types.js';
6
+ export declare const sanitizationScanner: Scanner;
7
+ export default sanitizationScanner;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AAGpE,eAAO,MAAM,mBAAmB,EAAE,OA8GjC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Input Sanitization Security Scanner
3
+ * Detects unsafe user input handling and file upload configurations
4
+ */
5
+ import * as fs from 'fs/promises';
6
+ import { ALL_SANITIZATION_PATTERNS } from './patterns.js';
7
+ export const sanitizationScanner = {
8
+ name: 'Input Sanitization Security Scanner',
9
+ category: 'access-control',
10
+ async scan(files, options) {
11
+ const findings = [];
12
+ // Filter to code files
13
+ const codeFiles = files.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f));
14
+ for (const file of codeFiles) {
15
+ try {
16
+ const content = await fs.readFile(file, 'utf-8');
17
+ const lines = content.split('\n');
18
+ for (let i = 0; i < lines.length; i++) {
19
+ const line = lines[i];
20
+ const lineNumber = i + 1;
21
+ // Skip empty lines and comments
22
+ if (/^\s*$/.test(line) || /^\s*\/\//.test(line))
23
+ continue;
24
+ // Scan each pattern
25
+ for (const pattern of ALL_SANITIZATION_PATTERNS) {
26
+ // Check if line matches violation pattern
27
+ const matched = pattern.patterns.some((regex) => regex.test(line));
28
+ if (!matched)
29
+ continue;
30
+ // Get surrounding context (10 lines before and 5 after), excluding comments
31
+ const contextStart = Math.max(0, i - 10);
32
+ const contextEnd = Math.min(lines.length, i + 6);
33
+ const contextLines = lines.slice(contextStart, contextEnd);
34
+ // Filter out comment lines from context
35
+ const codeOnlyContext = contextLines
36
+ .filter(l => !/^\s*\/\//.test(l) && !/^\s*\/\*/.test(l) && !/^\s*\*/.test(l))
37
+ .join('\n');
38
+ // Check negative patterns (safe usage indicators)
39
+ // For SANITIZE-001, check context for validation
40
+ // For SANITIZE-002, check if config object has required fields
41
+ const isSafe = pattern.negativePatterns?.some((regex) => {
42
+ const patternStr = regex.source;
43
+ // For file upload patterns, check the entire config block
44
+ if (pattern.id === 'SANITIZE-002') {
45
+ // Check if the line has the required validation fields
46
+ // or if they appear in the surrounding context
47
+ return regex.test(codeOnlyContext);
48
+ }
49
+ // For SANITIZE-001, check surrounding context for validation
50
+ if (pattern.id === 'SANITIZE-001') {
51
+ // Check if validation happens in surrounding code (excluding comments)
52
+ return regex.test(codeOnlyContext);
53
+ }
54
+ return regex.test(line);
55
+ });
56
+ if (isSafe)
57
+ continue;
58
+ // Additional check for SANITIZE-002: ensure it's an actual configuration
59
+ if (pattern.id === 'SANITIZE-002') {
60
+ // Skip if it's just a variable reference or import
61
+ if (/^import\s/i.test(line) ||
62
+ /^const\s+\w+\s*=\s*require/i.test(line)) {
63
+ continue;
64
+ }
65
+ // Only flag if it looks like actual middleware configuration
66
+ if (!/\bmulter\s*\(/i.test(line) &&
67
+ !/\bformidable\s*\(/i.test(line) &&
68
+ !/\bBusboy\s*\(/i.test(line) &&
69
+ !/IncomingForm\s*\(/i.test(line)) {
70
+ continue;
71
+ }
72
+ }
73
+ // Create finding
74
+ const finding = {
75
+ id: pattern.id,
76
+ category: pattern.category,
77
+ severity: pattern.severity,
78
+ title: pattern.name,
79
+ description: `${pattern.description}\n\nCode: ${line.trim()}`,
80
+ file: file,
81
+ line: lineNumber,
82
+ recommendation: pattern.recommendation,
83
+ hipaaReference: pattern.hipaaReference,
84
+ confidence: 'high',
85
+ };
86
+ findings.push(finding);
87
+ }
88
+ }
89
+ }
90
+ catch (error) {
91
+ // Skip files that can't be read
92
+ }
93
+ }
94
+ return findings;
95
+ },
96
+ };
97
+ export default sanitizationScanner;
98
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Input Sanitization Security Scanner Tests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Input Sanitization Security Scanner Tests
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import { sanitizationScanner } from './index.js';
6
+ import * as fs from 'fs/promises';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+ describe('Input Sanitization Security Scanner', () => {
10
+ let tempDir = '';
11
+ let testFiles = [];
12
+ beforeEach(async () => {
13
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sanitize-test-'));
14
+ });
15
+ afterEach(async () => {
16
+ // Cleanup
17
+ for (const file of testFiles) {
18
+ try {
19
+ await fs.unlink(file);
20
+ }
21
+ catch {
22
+ // Ignore
23
+ }
24
+ }
25
+ try {
26
+ await fs.rm(tempDir, { recursive: true, force: true });
27
+ }
28
+ catch {
29
+ // Ignore
30
+ }
31
+ testFiles = [];
32
+ });
33
+ async function createTestFile(filename, content) {
34
+ const filePath = path.join(tempDir, filename);
35
+ await fs.writeFile(filePath, content, 'utf-8');
36
+ testFiles.push(filePath);
37
+ return filePath;
38
+ }
39
+ const scanOptions = {
40
+ path: tempDir,
41
+ };
42
+ describe('SANITIZE-001: Unsanitized User Input in Database Operations', () => {
43
+ it('should detect req.body in insert operation', async () => {
44
+ const file = await createTestFile('db-insert.ts', `
45
+ // VIOLATION SANITIZE-001: Direct req.body in insert
46
+ app.post('/users', async (req, res) => {
47
+ await db.insert('users').values(req.body);
48
+ });
49
+ `);
50
+ const findings = await sanitizationScanner.scan([file], scanOptions);
51
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
52
+ expect(sanitizeFindings.length).toBeGreaterThan(0);
53
+ expect(sanitizeFindings[0].severity).toBe('critical');
54
+ });
55
+ it('should detect req.params in query operation', async () => {
56
+ const file = await createTestFile('db-query.ts', `
57
+ // VIOLATION SANITIZE-001: Direct req.params in query
58
+ app.get('/users/:id', async (req, res) => {
59
+ const user = await db.query('SELECT * FROM users WHERE id = ' + req.params.id);
60
+ });
61
+ `);
62
+ const findings = await sanitizationScanner.scan([file], scanOptions);
63
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
64
+ });
65
+ it('should detect req.query in where clause', async () => {
66
+ const file = await createTestFile('db-where.ts', `
67
+ // VIOLATION SANITIZE-001: Direct req.query in where
68
+ app.get('/search', async (req, res) => {
69
+ const results = await db.select().from('users').where('name', req.query.name);
70
+ });
71
+ `);
72
+ const findings = await sanitizationScanner.scan([file], scanOptions);
73
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
74
+ });
75
+ it('should detect req.body in update operation', async () => {
76
+ const file = await createTestFile('db-update.ts', `
77
+ // VIOLATION SANITIZE-001: Direct req.body in update
78
+ app.put('/users/:id', async (req, res) => {
79
+ await db.update('users').set(req.body).where({ id: req.params.id });
80
+ });
81
+ `);
82
+ const findings = await sanitizationScanner.scan([file], scanOptions);
83
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
84
+ expect(sanitizeFindings.length).toBeGreaterThan(0);
85
+ });
86
+ it('should detect req.body in Prisma create', async () => {
87
+ const file = await createTestFile('prisma-create.ts', `
88
+ // VIOLATION SANITIZE-001: Direct req.body in Prisma
89
+ app.post('/users', async (req, res) => {
90
+ const user = await prisma.user.create({ data: req.body });
91
+ });
92
+ `);
93
+ const findings = await sanitizationScanner.scan([file], scanOptions);
94
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
95
+ });
96
+ it('should detect req.body in Mongoose create', async () => {
97
+ const file = await createTestFile('mongoose-create.ts', `
98
+ // VIOLATION SANITIZE-001: Direct req.body in Mongoose
99
+ app.post('/users', async (req, res) => {
100
+ await User.create(req.body);
101
+ });
102
+ `);
103
+ const findings = await sanitizationScanner.scan([file], scanOptions);
104
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
105
+ });
106
+ it('should detect SQL template literals with req.query', async () => {
107
+ const file = await createTestFile('sql-template.ts', `
108
+ // VIOLATION SANITIZE-001: SQL injection via template literal
109
+ app.get('/users', async (req, res) => {
110
+ const results = await db.query(\`SELECT * FROM users WHERE email = '\${req.query.email}'\`);
111
+ });
112
+ `);
113
+ const findings = await sanitizationScanner.scan([file], scanOptions);
114
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
115
+ });
116
+ it('should detect raw SQL concatenation', async () => {
117
+ const file = await createTestFile('sql-concat.ts', `
118
+ // VIOLATION SANITIZE-001: SQL injection via concatenation
119
+ app.get('/search', async (req, res) => {
120
+ const sql = 'SELECT * FROM products WHERE name = ' + req.query.name;
121
+ await db.execute(sql);
122
+ });
123
+ `);
124
+ const findings = await sanitizationScanner.scan([file], scanOptions);
125
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
126
+ });
127
+ it('should NOT flag when Zod validation is used', async () => {
128
+ const file = await createTestFile('zod-validation.ts', `
129
+ // SECURE: Zod validation before DB operation
130
+ import { z } from 'zod';
131
+
132
+ const userSchema = z.object({
133
+ name: z.string(),
134
+ email: z.string().email(),
135
+ });
136
+
137
+ app.post('/users', async (req, res) => {
138
+ const validated = userSchema.parse(req.body);
139
+ await db.insert('users').values(validated);
140
+ });
141
+ `);
142
+ const findings = await sanitizationScanner.scan([file], scanOptions);
143
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
144
+ expect(sanitizeFindings.length).toBe(0);
145
+ });
146
+ it('should NOT flag when Joi validation is used', async () => {
147
+ const file = await createTestFile('joi-validation.ts', `
148
+ // SECURE: Joi validation
149
+ const Joi = require('joi');
150
+ const schema = Joi.object({ name: Joi.string() });
151
+
152
+ app.post('/users', async (req, res) => {
153
+ const { value } = schema.validate(req.body);
154
+ await db.insert('users').values(value);
155
+ });
156
+ `);
157
+ const findings = await sanitizationScanner.scan([file], scanOptions);
158
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
159
+ expect(sanitizeFindings.length).toBe(0);
160
+ });
161
+ it('should NOT flag when Yup validation is used', async () => {
162
+ const file = await createTestFile('yup-validation.ts', `
163
+ // SECURE: Yup validation
164
+ import * as yup from 'yup';
165
+
166
+ const schema = yup.object({ email: yup.string().email() });
167
+
168
+ app.post('/users', async (req, res) => {
169
+ const validated = await schema.validate(req.body);
170
+ await db.insert('users').values(validated);
171
+ });
172
+ `);
173
+ const findings = await sanitizationScanner.scan([file], scanOptions);
174
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
175
+ expect(sanitizeFindings.length).toBe(0);
176
+ });
177
+ it('should NOT flag when custom validation function is used', async () => {
178
+ const file = await createTestFile('custom-validation.ts', `
179
+ // SECURE: Custom validation
180
+ app.post('/users', async (req, res) => {
181
+ const sanitized = sanitizeUserInput(req.body);
182
+ await db.insert('users').values(sanitized);
183
+ });
184
+ `);
185
+ const findings = await sanitizationScanner.scan([file], scanOptions);
186
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
187
+ expect(sanitizeFindings.length).toBe(0);
188
+ });
189
+ it('should NOT flag when safeParse is used', async () => {
190
+ const file = await createTestFile('safe-parse.ts', `
191
+ // SECURE: safeParse validation
192
+ const result = schema.safeParse(req.body);
193
+ if (result.success) {
194
+ await db.insert('users').values(result.data);
195
+ }
196
+ `);
197
+ const findings = await sanitizationScanner.scan([file], scanOptions);
198
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
199
+ expect(sanitizeFindings.length).toBe(0);
200
+ });
201
+ });
202
+ describe('SANITIZE-002: Insecure File Upload Configuration', () => {
203
+ it('should detect multer without fileFilter', async () => {
204
+ const file = await createTestFile('multer-basic.ts', `
205
+ // VIOLATION SANITIZE-002: Multer without validation
206
+ const upload = multer({ dest: 'uploads/' });
207
+ app.post('/upload', upload.single('file'), (req, res) => {
208
+ res.send('File uploaded');
209
+ });
210
+ `);
211
+ const findings = await sanitizationScanner.scan([file], scanOptions);
212
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
213
+ expect(sanitizeFindings.length).toBeGreaterThan(0);
214
+ expect(sanitizeFindings[0].severity).toBe('high');
215
+ });
216
+ it('should detect multer without limits', async () => {
217
+ const file = await createTestFile('multer-no-limits.ts', `
218
+ // VIOLATION SANITIZE-002: Multer without file size limits
219
+ const upload = multer({
220
+ storage: multer.diskStorage({
221
+ destination: 'uploads/',
222
+ filename: (req, file, cb) => cb(null, file.originalname)
223
+ })
224
+ });
225
+ `);
226
+ const findings = await sanitizationScanner.scan([file], scanOptions);
227
+ expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
228
+ });
229
+ it('should detect formidable without validation', async () => {
230
+ const file = await createTestFile('formidable-basic.ts', `
231
+ // VIOLATION SANITIZE-002: Formidable without validation
232
+ app.post('/upload', (req, res) => {
233
+ const form = new formidable.IncomingForm({ uploadDir: './uploads' });
234
+ form.parse(req);
235
+ });
236
+ `);
237
+ const findings = await sanitizationScanner.scan([file], scanOptions);
238
+ expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
239
+ });
240
+ it('should detect Busboy without limits', async () => {
241
+ const file = await createTestFile('busboy-basic.ts', `
242
+ // VIOLATION SANITIZE-002: Busboy without limits
243
+ const busboy = new Busboy({ headers: req.headers });
244
+ busboy.on('file', (fieldname, file, filename) => {
245
+ file.pipe(fs.createWriteStream('./uploads/' + filename));
246
+ });
247
+ `);
248
+ const findings = await sanitizationScanner.scan([file], scanOptions);
249
+ expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
250
+ });
251
+ it('should NOT flag multer with fileFilter', async () => {
252
+ const file = await createTestFile('multer-secure.ts', `
253
+ // SECURE: Multer with fileFilter and limits
254
+ const upload = multer({
255
+ dest: 'uploads/',
256
+ fileFilter: (req, file, cb) => {
257
+ const allowedMimes = ['image/jpeg', 'image/png'];
258
+ if (allowedMimes.includes(file.mimetype)) {
259
+ cb(null, true);
260
+ } else {
261
+ cb(new Error('Invalid file type'));
262
+ }
263
+ },
264
+ limits: { fileSize: 5 * 1024 * 1024 }
265
+ });
266
+ `);
267
+ const findings = await sanitizationScanner.scan([file], scanOptions);
268
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
269
+ expect(sanitizeFindings.length).toBe(0);
270
+ });
271
+ it('should NOT flag multer with mimetype validation', async () => {
272
+ const file = await createTestFile('multer-mimetype.ts', `
273
+ // SECURE: Multer with mimetype validation
274
+ const upload = multer({
275
+ storage: multer.diskStorage({
276
+ destination: 'uploads/',
277
+ filename: (req, file, cb) => {
278
+ if (!file.mimetype.startsWith('image/')) {
279
+ return cb(new Error('Invalid file type'));
280
+ }
281
+ cb(null, file.originalname);
282
+ }
283
+ })
284
+ });
285
+ `);
286
+ const findings = await sanitizationScanner.scan([file], scanOptions);
287
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
288
+ expect(sanitizeFindings.length).toBe(0);
289
+ });
290
+ it('should NOT flag formidable with file validation', async () => {
291
+ const file = await createTestFile('formidable-secure.ts', `
292
+ // SECURE: Formidable with validation
293
+ const form = new formidable.IncomingForm({
294
+ uploadDir: './uploads',
295
+ maxFileSize: 10 * 1024 * 1024,
296
+ filter: function ({ name, originalFilename, mimetype }) {
297
+ return mimetype && mimetype.includes('image');
298
+ }
299
+ });
300
+ `);
301
+ const findings = await sanitizationScanner.scan([file], scanOptions);
302
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
303
+ expect(sanitizeFindings.length).toBe(0);
304
+ });
305
+ it('should NOT flag Busboy with limits and validation', async () => {
306
+ const file = await createTestFile('busboy-secure.ts', `
307
+ // SECURE: Busboy with limits
308
+ const busboy = new Busboy({
309
+ headers: req.headers,
310
+ limits: {
311
+ fileSize: 10 * 1024 * 1024,
312
+ files: 1
313
+ }
314
+ });
315
+
316
+ busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
317
+ if (!mimetype.startsWith('image/')) {
318
+ file.resume();
319
+ return;
320
+ }
321
+ file.pipe(fs.createWriteStream('./uploads/' + filename));
322
+ });
323
+ `);
324
+ const findings = await sanitizationScanner.scan([file], scanOptions);
325
+ const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
326
+ expect(sanitizeFindings.length).toBe(0);
327
+ });
328
+ });
329
+ describe('Combined violations', () => {
330
+ it('should detect both SANITIZE-001 and SANITIZE-002 in same file', async () => {
331
+ const file = await createTestFile('combined.ts', `
332
+ const upload = multer({ dest: 'uploads/' });
333
+
334
+ app.post('/upload-profile', upload.single('avatar'), async (req, res) => {
335
+ await db.insert('users').values(req.body);
336
+ });
337
+ `);
338
+ const findings = await sanitizationScanner.scan([file], scanOptions);
339
+ expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
340
+ expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
341
+ });
342
+ });
343
+ it('should provide correct HIPAA references', async () => {
344
+ const file = await createTestFile('hipaa-refs.ts', `
345
+ app.post('/test', async (req, res) => {
346
+ await db.insert('users').values(req.body);
347
+ const upload = multer({ dest: 'uploads/' });
348
+ });
349
+ `);
350
+ const findings = await sanitizationScanner.scan([file], scanOptions);
351
+ const sanitize001 = findings.find((f) => f.id === 'SANITIZE-001');
352
+ const sanitize002 = findings.find((f) => f.id === 'SANITIZE-002');
353
+ expect(sanitize001?.hipaaReference).toContain('NPRM Anti-malware');
354
+ expect(sanitize002?.hipaaReference).toContain('NPRM Anti-malware');
355
+ });
356
+ it('should have correct severity levels', async () => {
357
+ const file = await createTestFile('severity.ts', `
358
+ app.post('/test', async (req, res) => {
359
+ await db.query(req.body);
360
+ });
361
+ const upload = multer({});
362
+ `);
363
+ const findings = await sanitizationScanner.scan([file], scanOptions);
364
+ const sanitize001 = findings.find((f) => f.id === 'SANITIZE-001');
365
+ const sanitize002 = findings.find((f) => f.id === 'SANITIZE-002');
366
+ expect(sanitize001?.severity).toBe('critical');
367
+ expect(sanitize002?.severity).toBe('high');
368
+ });
369
+ });
370
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;IACnD,IAAI,OAAO,GAAW,EAAE,CAAC;IACzB,IAAI,SAAS,GAAa,EAAE,CAAC;IAE7B,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,UAAU;QACV,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,SAAS,GAAG,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,cAAc,CAC3B,QAAgB,EAChB,OAAe;QAEf,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GAAgB;QAC/B,IAAI,EAAE,OAAO;KACd,CAAC;IAEF,QAAQ,CAAC,6DAA6D,EAAE,GAAG,EAAE;QAC3E,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,cAAc,EACd;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACnD,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,aAAa,EACb;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,aAAa,EACb;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;YAC1D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,cAAc,EACd;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,kBAAkB,EAClB;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,oBAAoB,EACpB;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;YAClE,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,iBAAiB,EACjB;;;;;CAKP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,eAAe,EACf;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,mBAAmB,EACnB;;;;;;;;;;;;;CAaP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,mBAAmB,EACnB;;;;;;;;;CASP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,mBAAmB,EACnB;;;;;;;;;;CAUP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;YACvE,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,sBAAsB,EACtB;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,eAAe,EACf;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAChE,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,iBAAiB,EACjB;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;YACnD,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,qBAAqB,EACrB;;;;;;;;CAQP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,qBAAqB,EACrB;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,iBAAiB,EACjB;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,kBAAkB,EAClB;;;;;;;;;;;;;;CAcP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,oBAAoB,EACpB;;;;;;;;;;;;;CAaP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,sBAAsB,EACtB;;;;;;;;;CASP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;YACjE,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,kBAAkB,EAClB;;;;;;;;;;;;;;;;;CAiBP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;YAC7E,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,aAAa,EACb;;;;;;CAMP,CACM,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;YACrE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,eAAe,EACf;;;;;CAKL,CACI,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;QAElE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACnE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,aAAa,EACb;;;;;CAKL,CACI,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;QAElE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}