getdoorman 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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/bin/doorman.js +444 -0
  4. package/package.json +74 -0
  5. package/src/ai-fixer.js +559 -0
  6. package/src/ast-scanner.js +434 -0
  7. package/src/auth.js +149 -0
  8. package/src/baseline.js +48 -0
  9. package/src/compliance.js +539 -0
  10. package/src/config.js +466 -0
  11. package/src/custom-rules.js +32 -0
  12. package/src/dashboard.js +202 -0
  13. package/src/detector.js +142 -0
  14. package/src/fix-engine.js +48 -0
  15. package/src/fix-registry-extra.js +95 -0
  16. package/src/fix-registry-go-rust.js +77 -0
  17. package/src/fix-registry-java-csharp.js +77 -0
  18. package/src/fix-registry-js.js +99 -0
  19. package/src/fix-registry-mcp-ai.js +57 -0
  20. package/src/fix-registry-python.js +87 -0
  21. package/src/fixer-ruby-php.js +608 -0
  22. package/src/fixer.js +2113 -0
  23. package/src/hooks.js +115 -0
  24. package/src/ignore.js +176 -0
  25. package/src/index.js +384 -0
  26. package/src/metrics.js +126 -0
  27. package/src/monorepo.js +65 -0
  28. package/src/presets.js +54 -0
  29. package/src/reporter.js +975 -0
  30. package/src/rule-worker.js +36 -0
  31. package/src/rules/ast-rules.js +756 -0
  32. package/src/rules/bugs/accessibility.js +235 -0
  33. package/src/rules/bugs/ai-codegen-fixable.js +172 -0
  34. package/src/rules/bugs/ai-codegen.js +365 -0
  35. package/src/rules/bugs/code-smell-bugs.js +247 -0
  36. package/src/rules/bugs/crypto-bugs.js +195 -0
  37. package/src/rules/bugs/docker-bugs.js +158 -0
  38. package/src/rules/bugs/general.js +361 -0
  39. package/src/rules/bugs/go-bugs.js +279 -0
  40. package/src/rules/bugs/index.js +73 -0
  41. package/src/rules/bugs/js-api.js +257 -0
  42. package/src/rules/bugs/js-array-object.js +210 -0
  43. package/src/rules/bugs/js-async-fixable.js +223 -0
  44. package/src/rules/bugs/js-async.js +211 -0
  45. package/src/rules/bugs/js-closure-scope.js +182 -0
  46. package/src/rules/bugs/js-database.js +203 -0
  47. package/src/rules/bugs/js-error-handling.js +148 -0
  48. package/src/rules/bugs/js-logic.js +261 -0
  49. package/src/rules/bugs/js-memory.js +214 -0
  50. package/src/rules/bugs/js-node.js +361 -0
  51. package/src/rules/bugs/js-react.js +373 -0
  52. package/src/rules/bugs/js-regex.js +200 -0
  53. package/src/rules/bugs/js-state.js +272 -0
  54. package/src/rules/bugs/js-type-coercion.js +318 -0
  55. package/src/rules/bugs/nextjs-bugs.js +242 -0
  56. package/src/rules/bugs/nextjs-fixable.js +120 -0
  57. package/src/rules/bugs/node-fixable.js +178 -0
  58. package/src/rules/bugs/python-advanced.js +245 -0
  59. package/src/rules/bugs/python-fixable.js +98 -0
  60. package/src/rules/bugs/python.js +284 -0
  61. package/src/rules/bugs/react-fixable.js +207 -0
  62. package/src/rules/bugs/ruby-bugs.js +182 -0
  63. package/src/rules/bugs/shell-bugs.js +181 -0
  64. package/src/rules/bugs/silent-failures.js +261 -0
  65. package/src/rules/bugs/ts-bugs.js +235 -0
  66. package/src/rules/bugs/unused-vars.js +65 -0
  67. package/src/rules/compliance/accessibility-ext.js +468 -0
  68. package/src/rules/compliance/education.js +322 -0
  69. package/src/rules/compliance/financial.js +421 -0
  70. package/src/rules/compliance/frameworks.js +507 -0
  71. package/src/rules/compliance/healthcare.js +520 -0
  72. package/src/rules/compliance/index.js +2714 -0
  73. package/src/rules/compliance/regional-eu.js +480 -0
  74. package/src/rules/compliance/regional-international.js +903 -0
  75. package/src/rules/cost/index.js +1993 -0
  76. package/src/rules/data/index.js +2503 -0
  77. package/src/rules/dependencies/index.js +1684 -0
  78. package/src/rules/deployment/index.js +2050 -0
  79. package/src/rules/index.js +71 -0
  80. package/src/rules/infrastructure/index.js +3048 -0
  81. package/src/rules/performance/index.js +3455 -0
  82. package/src/rules/quality/index.js +3175 -0
  83. package/src/rules/reliability/index.js +3040 -0
  84. package/src/rules/scope-rules.js +815 -0
  85. package/src/rules/security/ai-api.js +1177 -0
  86. package/src/rules/security/auth.js +1328 -0
  87. package/src/rules/security/cors.js +127 -0
  88. package/src/rules/security/crypto.js +527 -0
  89. package/src/rules/security/csharp.js +862 -0
  90. package/src/rules/security/csrf.js +193 -0
  91. package/src/rules/security/dart.js +835 -0
  92. package/src/rules/security/deserialization.js +291 -0
  93. package/src/rules/security/file-upload.js +187 -0
  94. package/src/rules/security/go.js +850 -0
  95. package/src/rules/security/headers.js +235 -0
  96. package/src/rules/security/index.js +65 -0
  97. package/src/rules/security/injection.js +1639 -0
  98. package/src/rules/security/mcp-server.js +71 -0
  99. package/src/rules/security/misconfiguration.js +660 -0
  100. package/src/rules/security/oauth-jwt.js +329 -0
  101. package/src/rules/security/path-traversal.js +295 -0
  102. package/src/rules/security/php.js +1054 -0
  103. package/src/rules/security/prototype-pollution.js +283 -0
  104. package/src/rules/security/rate-limiting.js +208 -0
  105. package/src/rules/security/ruby.js +1061 -0
  106. package/src/rules/security/rust.js +693 -0
  107. package/src/rules/security/secrets.js +747 -0
  108. package/src/rules/security/shell.js +647 -0
  109. package/src/rules/security/ssrf.js +298 -0
  110. package/src/rules/security/supply-chain-advanced.js +393 -0
  111. package/src/rules/security/supply-chain.js +734 -0
  112. package/src/rules/security/swift.js +835 -0
  113. package/src/rules/security/taint.js +27 -0
  114. package/src/rules/security/xss.js +520 -0
  115. package/src/scan-cache.js +71 -0
  116. package/src/scanner.js +710 -0
  117. package/src/scope-analyzer.js +685 -0
  118. package/src/share.js +88 -0
  119. package/src/taint.js +300 -0
  120. package/src/telemetry.js +183 -0
  121. package/src/tracer.js +190 -0
  122. package/src/upload.js +35 -0
  123. package/src/worker.js +31 -0
@@ -0,0 +1,421 @@
1
+ function isSourceFile(f) { return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go', '.java', '.cs', '.php'].some(e => f.endsWith(e)); }
2
+
3
+ const financialPattern = /(?:account.*number|routing.*number|credit.*score|income|salary|tax.*return|financial|banking|loan|mortgage|investment|portfolio)/i;
4
+ const npiPattern = /(?:account[_\-.]?number|routing[_\-.]?number|ssn|social[_\-.]?security|credit[_\-.]?score|income|salary|tax[_\-.]?return|net[_\-.]?worth|bank[_\-.]?balance|loan[_\-.]?amount)/i;
5
+ const financialDataPattern = /(?:financial[_\-.]?(?:data|record|report|transaction|statement)|account[_\-.]?(?:balance|number|detail)|transaction|payment|ledger|revenue|expense)/i;
6
+
7
+ function hasFinancialContext(files) {
8
+ return [...files.entries()].some(([f, c]) => isSourceFile(f) && financialPattern.test(c));
9
+ }
10
+
11
+ const rules = [
12
+ // === GLBA (Gramm-Leach-Bliley Act) Rules ===
13
+
14
+ {
15
+ id: 'COMP-GLBA-001',
16
+ category: 'compliance',
17
+ severity: 'high',
18
+ confidence: 'likely',
19
+ title: 'No Privacy Notice',
20
+ check({ files }) {
21
+ const findings = [];
22
+ if (!hasFinancialContext(files)) return findings;
23
+ const privacyNoticePattern = /(?:privacy[_\-.]?noti(?:ce|fication)|privacy[_\-.]?policy|privacy[_\-.]?disclosure|glba[_\-.]?notice|gramm[_\-.]?leach|financial[_\-.]?privacy)/i;
24
+ const hasNotice = [...files.entries()].some(([f, c]) => privacyNoticePattern.test(c) || f.match(/privacy/i));
25
+ if (!hasNotice) {
26
+ findings.push({
27
+ ruleId: 'COMP-GLBA-001', category: 'compliance', severity: 'high',
28
+ title: 'Financial application without privacy notice/disclosure',
29
+ description: 'GLBA requires financial institutions to provide customers with a privacy notice explaining what personal financial information is collected, how it is used, and how it is protected.',
30
+ fix: null,
31
+ });
32
+ }
33
+ return findings;
34
+ },
35
+ },
36
+
37
+ {
38
+ id: 'COMP-GLBA-002',
39
+ category: 'compliance',
40
+ severity: 'high',
41
+ confidence: 'likely',
42
+ title: 'NPI Without Encryption',
43
+ check({ files }) {
44
+ const findings = [];
45
+ if (!hasFinancialContext(files)) return findings;
46
+ const encryptionPattern = /(?:encrypt|cipher|aes|crypto|tls|ssl|https|bcrypt|argon|scrypt|kms|vault|secure[_\-.]?storage)/i;
47
+ for (const [filepath, content] of files.entries()) {
48
+ if (!isSourceFile(filepath)) continue;
49
+ if (!npiPattern.test(content)) continue;
50
+ const lines = content.split('\n');
51
+ for (let i = 0; i < lines.length; i++) {
52
+ if (npiPattern.test(lines[i]) && /(?:store|save|send|transmit|write|insert|log|print|console)/i.test(lines[i])) {
53
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(lines.length, i + 10)).join('\n');
54
+ if (!encryptionPattern.test(contextBlock)) {
55
+ findings.push({
56
+ ruleId: 'COMP-GLBA-002', category: 'compliance', severity: 'high',
57
+ title: 'Nonpublic personal information handled without encryption',
58
+ description: 'GLBA Safeguards Rule requires encryption of nonpublic personal information (NPI) including account numbers, SSNs, and income data both in transit and at rest.',
59
+ file: filepath, line: i + 1, fix: null,
60
+ });
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return findings;
66
+ },
67
+ },
68
+
69
+ {
70
+ id: 'COMP-GLBA-003',
71
+ category: 'compliance',
72
+ severity: 'medium',
73
+ confidence: 'likely',
74
+ title: 'Missing Information Security Program',
75
+ check({ files }) {
76
+ const findings = [];
77
+ if (!hasFinancialContext(files)) return findings;
78
+ const securityProgramPattern = /(?:security[_\-.]?program|infosec[_\-.]?policy|information[_\-.]?security[_\-.]?(?:policy|program|plan)|safeguards[_\-.]?rule|glba[_\-.]?compliance|security[_\-.]?framework)/i;
79
+ const hasProgram = [...files.entries()].some(([f, c]) => securityProgramPattern.test(c) || f.match(/security[_\-.]?(?:policy|program)/i));
80
+ if (!hasProgram) {
81
+ findings.push({
82
+ ruleId: 'COMP-GLBA-003', category: 'compliance', severity: 'medium',
83
+ title: 'Financial application without information security program reference',
84
+ description: 'GLBA Safeguards Rule requires financial institutions to develop, implement, and maintain a comprehensive information security program. Reference your security program in the codebase.',
85
+ fix: null,
86
+ });
87
+ }
88
+ return findings;
89
+ },
90
+ },
91
+
92
+ {
93
+ id: 'COMP-GLBA-004',
94
+ category: 'compliance',
95
+ severity: 'high',
96
+ confidence: 'likely',
97
+ title: 'No Opt-Out for Information Sharing',
98
+ check({ files }) {
99
+ const findings = [];
100
+ if (!hasFinancialContext(files)) return findings;
101
+ const sharingPattern = /(?:share|disclose|transfer|send|provide).*(?:third[_\-.]?party|partner|affiliate|vendor|external)/i;
102
+ const optOutPattern = /(?:opt[_\-.]?out|do[_\-.]?not[_\-.]?share|unsubscribe|sharing[_\-.]?preference|privacy[_\-.]?choice|consent[_\-.]?management)/i;
103
+ for (const [filepath, content] of files.entries()) {
104
+ if (!isSourceFile(filepath)) continue;
105
+ if (!financialPattern.test(content)) continue;
106
+ if (sharingPattern.test(content) && !optOutPattern.test(content)) {
107
+ const lines = content.split('\n');
108
+ for (let i = 0; i < lines.length; i++) {
109
+ if (sharingPattern.test(lines[i])) {
110
+ findings.push({
111
+ ruleId: 'COMP-GLBA-004', category: 'compliance', severity: 'high',
112
+ title: 'Financial data shared with third parties without opt-out mechanism',
113
+ description: 'GLBA requires financial institutions to provide customers the right to opt out of having their NPI shared with nonaffiliated third parties.',
114
+ file: filepath, line: i + 1, fix: null,
115
+ });
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return findings;
122
+ },
123
+ },
124
+
125
+ {
126
+ id: 'COMP-GLBA-005',
127
+ category: 'compliance',
128
+ severity: 'high',
129
+ confidence: 'likely',
130
+ title: 'Third-Party Sharing Without Agreement',
131
+ check({ files }) {
132
+ const findings = [];
133
+ if (!hasFinancialContext(files)) return findings;
134
+ const thirdPartyPattern = /(?:vendor|partner|contractor|third[_\-.]?party|external[_\-.]?(?:service|api|provider)|outsource)/i;
135
+ const agreementPattern = /(?:agreement|contract|dpa|data[_\-.]?processing|service[_\-.]?level|sla|terms|compliance[_\-.]?requirement)/i;
136
+ for (const [filepath, content] of files.entries()) {
137
+ if (!isSourceFile(filepath)) continue;
138
+ if (!financialDataPattern.test(content)) continue;
139
+ const lines = content.split('\n');
140
+ for (let i = 0; i < lines.length; i++) {
141
+ if (thirdPartyPattern.test(lines[i]) && financialDataPattern.test(content)) {
142
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(lines.length, i + 10)).join('\n');
143
+ if (!agreementPattern.test(contextBlock) && !agreementPattern.test(content)) {
144
+ findings.push({
145
+ ruleId: 'COMP-GLBA-005', category: 'compliance', severity: 'high',
146
+ title: 'Financial data shared with vendor without service agreement',
147
+ description: 'GLBA requires contractual agreements with service providers that handle customer financial information, ensuring they maintain appropriate safeguards.',
148
+ file: filepath, line: i + 1, fix: null,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return findings;
155
+ },
156
+ },
157
+
158
+ {
159
+ id: 'COMP-GLBA-006',
160
+ category: 'compliance',
161
+ severity: 'medium',
162
+ confidence: 'likely',
163
+ title: 'Missing Risk Assessment',
164
+ check({ files }) {
165
+ const findings = [];
166
+ if (!hasFinancialContext(files)) return findings;
167
+ const riskPattern = /(?:risk[_\-.]?assessment|threat[_\-.]?model|vulnerability[_\-.]?assessment|security[_\-.]?audit|penetration[_\-.]?test|risk[_\-.]?analysis|security[_\-.]?review)/i;
168
+ const hasRisk = [...files.entries()].some(([f, c]) => riskPattern.test(c) || f.match(/risk/i));
169
+ if (!hasRisk) {
170
+ findings.push({
171
+ ruleId: 'COMP-GLBA-006', category: 'compliance', severity: 'medium',
172
+ title: 'Financial application without risk assessment reference',
173
+ description: 'GLBA Safeguards Rule requires regular risk assessments to identify threats to customer information. Reference risk assessment processes in your financial application.',
174
+ fix: null,
175
+ });
176
+ }
177
+ return findings;
178
+ },
179
+ },
180
+
181
+ {
182
+ id: 'COMP-GLBA-007',
183
+ category: 'compliance',
184
+ severity: 'medium',
185
+ confidence: 'likely',
186
+ title: 'No Incident Response Plan',
187
+ check({ files }) {
188
+ const findings = [];
189
+ if (!hasFinancialContext(files)) return findings;
190
+ const incidentPattern = /(?:incident[_\-.]?response|breach[_\-.]?notification|security[_\-.]?incident|incident[_\-.]?plan|playbook|escalation|on[_\-.]?call|pager[_\-.]?duty|alert[_\-.]?handler)/i;
191
+ const hasIncident = [...files.entries()].some(([f, c]) => incidentPattern.test(c) || f.match(/incident/i));
192
+ if (!hasIncident) {
193
+ findings.push({
194
+ ruleId: 'COMP-GLBA-007', category: 'compliance', severity: 'medium',
195
+ title: 'Financial application without incident response plan',
196
+ description: 'GLBA requires financial institutions to have an incident response plan for unauthorized access to customer information. Implement and reference an incident response process.',
197
+ fix: null,
198
+ });
199
+ }
200
+ return findings;
201
+ },
202
+ },
203
+
204
+ {
205
+ id: 'COMP-GLBA-008',
206
+ category: 'compliance',
207
+ severity: 'high',
208
+ confidence: 'likely',
209
+ title: 'Employee Access Without Need-to-Know',
210
+ check({ files }) {
211
+ const findings = [];
212
+ if (!hasFinancialContext(files)) return findings;
213
+ const rolePattern = /(?:rbac|role[_\-.]?based|role[_\-.]?check|hasRole|checkRole|requireRole|permission|privilege|need[_\-.]?to[_\-.]?know|least[_\-.]?privilege|access[_\-.]?level)/i;
214
+ for (const [filepath, content] of files.entries()) {
215
+ if (!isSourceFile(filepath)) continue;
216
+ if (!financialDataPattern.test(content)) continue;
217
+ if (/(?:endpoint|route|api|handler|controller)/i.test(content)) {
218
+ if (!rolePattern.test(content)) {
219
+ findings.push({
220
+ ruleId: 'COMP-GLBA-008', category: 'compliance', severity: 'high',
221
+ title: 'Broad access to financial data without role-based restrictions',
222
+ description: 'GLBA requires limiting access to customer NPI to employees with a legitimate business need. Implement role-based access controls (RBAC) with need-to-know restrictions.',
223
+ file: filepath, fix: null,
224
+ });
225
+ }
226
+ }
227
+ }
228
+ return findings;
229
+ },
230
+ },
231
+
232
+ // === SOX (Sarbanes-Oxley) Rules ===
233
+
234
+ {
235
+ id: 'COMP-SOX-001',
236
+ category: 'compliance',
237
+ severity: 'high',
238
+ confidence: 'likely',
239
+ title: 'Financial Data Without Audit Trail',
240
+ check({ files }) {
241
+ const findings = [];
242
+ if (!hasFinancialContext(files)) return findings;
243
+ const auditPattern = /(?:audit[_\-.]?(?:log|trail|record|entry)|access[_\-.]?log|logAccess|auditLog|transaction[_\-.]?log|change[_\-.]?log|history[_\-.]?table|event[_\-.]?sourcing)/i;
244
+ const transactionPattern = /(?:transaction|transfer|payment|debit|credit|ledger|journal[_\-.]?entry|posting|reconcil)/i;
245
+ for (const [filepath, content] of files.entries()) {
246
+ if (!isSourceFile(filepath)) continue;
247
+ if (!transactionPattern.test(content)) continue;
248
+ if (!financialPattern.test(content)) continue;
249
+ if (!auditPattern.test(content)) {
250
+ findings.push({
251
+ ruleId: 'COMP-SOX-001', category: 'compliance', severity: 'high',
252
+ title: 'Financial transactions without audit logging',
253
+ description: 'SOX Section 302/404 requires internal controls over financial reporting, including audit trails for all financial transactions. Implement comprehensive audit logging.',
254
+ file: filepath, fix: null,
255
+ });
256
+ }
257
+ }
258
+ return findings;
259
+ },
260
+ },
261
+
262
+ {
263
+ id: 'COMP-SOX-002',
264
+ category: 'compliance',
265
+ severity: 'high',
266
+ confidence: 'likely',
267
+ title: 'Missing Access Controls on Financial Systems',
268
+ check({ files }) {
269
+ const findings = [];
270
+ if (!hasFinancialContext(files)) return findings;
271
+ const accessControlPattern = /(?:rbac|role[_\-.]?based|authorize|checkPermission|requireRole|access[_\-.]?control|acl|policy[_\-.]?enforcement|auth[_\-.]?middleware)/i;
272
+ for (const [filepath, content] of files.entries()) {
273
+ if (!isSourceFile(filepath)) continue;
274
+ if (!financialDataPattern.test(content)) continue;
275
+ if (/(?:endpoint|route|api|handler|controller)/i.test(content)) {
276
+ if (!accessControlPattern.test(content)) {
277
+ findings.push({
278
+ ruleId: 'COMP-SOX-002', category: 'compliance', severity: 'high',
279
+ title: 'Financial endpoint without role-based access controls',
280
+ description: 'SOX requires strict access controls on financial systems. Implement RBAC to ensure only authorized personnel can access financial data and operations.',
281
+ file: filepath, fix: null,
282
+ });
283
+ }
284
+ }
285
+ }
286
+ return findings;
287
+ },
288
+ },
289
+
290
+ {
291
+ id: 'COMP-SOX-003',
292
+ category: 'compliance',
293
+ severity: 'medium',
294
+ confidence: 'likely',
295
+ title: 'No Change Management',
296
+ check({ files }) {
297
+ const findings = [];
298
+ if (!hasFinancialContext(files)) return findings;
299
+ const changeManagementPattern = /(?:change[_\-.]?management|change[_\-.]?request|approval[_\-.]?workflow|review[_\-.]?required|code[_\-.]?review|pull[_\-.]?request|merge[_\-.]?approval|change[_\-.]?advisory|cab|release[_\-.]?management)/i;
300
+ const hasChangeManagement = [...files.entries()].some(([f, c]) =>
301
+ changeManagementPattern.test(c) || f.match(/\.github\/(?:workflows|CODEOWNERS)/i) || f.match(/CODEOWNERS/i)
302
+ );
303
+ if (!hasChangeManagement) {
304
+ findings.push({
305
+ ruleId: 'COMP-SOX-003', category: 'compliance', severity: 'medium',
306
+ title: 'Financial code without change management/approval process',
307
+ description: 'SOX requires documented change management processes for financial systems. Implement code review requirements, approval workflows, and change tracking.',
308
+ fix: null,
309
+ });
310
+ }
311
+ return findings;
312
+ },
313
+ },
314
+
315
+ {
316
+ id: 'COMP-SOX-004',
317
+ category: 'compliance',
318
+ severity: 'high',
319
+ confidence: 'likely',
320
+ title: 'Financial Reports Without Integrity Checks',
321
+ check({ files }) {
322
+ const findings = [];
323
+ if (!hasFinancialContext(files)) return findings;
324
+ const reportPattern = /(?:financial[_\-.]?report|balance[_\-.]?sheet|income[_\-.]?statement|cash[_\-.]?flow|quarterly[_\-.]?report|annual[_\-.]?report|generate[_\-.]?report|report[_\-.]?generat)/i;
325
+ const integrityPattern = /(?:checksum|hash|signature|verify|validate|integrity|hmac|digest|reconcil|cross[_\-.]?check|balance[_\-.]?check)/i;
326
+ for (const [filepath, content] of files.entries()) {
327
+ if (!isSourceFile(filepath)) continue;
328
+ if (!reportPattern.test(content)) continue;
329
+ if (!integrityPattern.test(content)) {
330
+ findings.push({
331
+ ruleId: 'COMP-SOX-004', category: 'compliance', severity: 'high',
332
+ title: 'Financial report generation without integrity verification',
333
+ description: 'SOX requires internal controls ensuring the accuracy and integrity of financial reports. Implement checksums, validation, or reconciliation checks in report generation.',
334
+ file: filepath, fix: null,
335
+ });
336
+ }
337
+ }
338
+ return findings;
339
+ },
340
+ },
341
+
342
+ {
343
+ id: 'COMP-SOX-005',
344
+ category: 'compliance',
345
+ severity: 'medium',
346
+ confidence: 'likely',
347
+ title: 'Missing Data Retention for Financial Records',
348
+ check({ files }) {
349
+ const findings = [];
350
+ if (!hasFinancialContext(files)) return findings;
351
+ const retentionPattern = /(?:retention|archive|preserve|7[_\-.]?year|seven[_\-.]?year|data[_\-.]?lifecycle|immutable|worm|write[_\-.]?once|long[_\-.]?term[_\-.]?storage)/i;
352
+ const hasRetention = [...files.entries()].some(([f, c]) => isSourceFile(f) && retentionPattern.test(c) && financialDataPattern.test(c));
353
+ if (!hasRetention) {
354
+ const hasFinancialStorage = [...files.entries()].some(([f, c]) =>
355
+ isSourceFile(f) && financialDataPattern.test(c) && /(?:save|store|insert|create|persist|database|collection|table)/i.test(c)
356
+ );
357
+ if (hasFinancialStorage) {
358
+ findings.push({
359
+ ruleId: 'COMP-SOX-005', category: 'compliance', severity: 'medium',
360
+ title: 'Financial data without 7-year retention policy',
361
+ description: 'SOX requires financial records to be retained for at least 7 years. Implement data retention policies and archival mechanisms for all financial records.',
362
+ fix: null,
363
+ });
364
+ }
365
+ }
366
+ return findings;
367
+ },
368
+ },
369
+
370
+ {
371
+ id: 'COMP-SOX-006',
372
+ category: 'compliance',
373
+ severity: 'high',
374
+ confidence: 'likely',
375
+ title: 'No Segregation of Duties',
376
+ check({ files }) {
377
+ const findings = [];
378
+ if (!hasFinancialContext(files)) return findings;
379
+ const sodPattern = /(?:segregat(?:ion|e)[_\-.]?of[_\-.]?dut(?:y|ies)|dual[_\-.]?control|four[_\-.]?eyes|two[_\-.]?person|maker[_\-.]?checker|approval[_\-.]?workflow|separate[_\-.]?approver|cannot[_\-.]?approve[_\-.]?own)/i;
380
+ const approveExecutePattern = /(?:approve|authorize).*(?:execute|process|submit|transfer|payment)/i;
381
+ for (const [filepath, content] of files.entries()) {
382
+ if (!isSourceFile(filepath)) continue;
383
+ if (!financialDataPattern.test(content)) continue;
384
+ if (approveExecutePattern.test(content) && !sodPattern.test(content)) {
385
+ findings.push({
386
+ ruleId: 'COMP-SOX-006', category: 'compliance', severity: 'high',
387
+ title: 'Financial operations without segregation of duties',
388
+ description: 'SOX requires segregation of duties so that the same person cannot both approve and execute financial transactions. Implement maker-checker or dual-control patterns.',
389
+ file: filepath, fix: null,
390
+ });
391
+ }
392
+ }
393
+ return findings;
394
+ },
395
+ },
396
+
397
+ {
398
+ id: 'COMP-SOX-007',
399
+ category: 'compliance',
400
+ severity: 'medium',
401
+ confidence: 'likely',
402
+ title: 'Financial System Without Backup/Recovery',
403
+ check({ files }) {
404
+ const findings = [];
405
+ if (!hasFinancialContext(files)) return findings;
406
+ const backupPattern = /(?:backup|disaster[_\-.]?recovery|failover|replicat|snapshot|point[_\-.]?in[_\-.]?time|restore|recovery[_\-.]?point|rpo|rto|business[_\-.]?continuity)/i;
407
+ const hasBackup = [...files.entries()].some(([f, c]) => backupPattern.test(c) || f.match(/backup|disaster|recovery/i));
408
+ if (!hasBackup) {
409
+ findings.push({
410
+ ruleId: 'COMP-SOX-007', category: 'compliance', severity: 'medium',
411
+ title: 'Financial application without backup/recovery references',
412
+ description: 'SOX requires financial systems to have adequate backup and disaster recovery procedures to ensure business continuity and data preservation. Implement backup and recovery mechanisms.',
413
+ fix: null,
414
+ });
415
+ }
416
+ return findings;
417
+ },
418
+ },
419
+ ];
420
+
421
+ export default rules;