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,520 @@
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 PHI_FIELDS = /(?:patient|diagnosis|medication|treatment|prescription|medical_record|health_plan|insurance_id|dob|date_of_birth|ssn|medical|clinical|lab_result|vital_sign|allergy|immunization|discharge|prognosis)/i;
4
+ const HEALTH_ENDPOINT = /(?:\/(?:api\/)?(?:patient|health|medical|clinical|appointment|prescription|diagnosis|ehr|fhir|telehealth))/i;
5
+
6
+ // Only flag HIPAA rules if the project is clearly a healthcare app.
7
+ // Require: health-specific dependencies OR dedicated health API routes (3+ matches).
8
+ const HEALTH_DEPS = /(?:hl7|fhir|@medplum|openemr|smart-on-fhir|bluebutton|healthcare|hipaa|phi-|epic-client)/i;
9
+ function hasHealthContext(files) {
10
+ let healthEndpointCount = 0;
11
+ let hasHealthDep = false;
12
+ for (const [fp, c] of files) {
13
+ if (fp.endsWith('package.json') || fp.endsWith('requirements.txt') || fp.endsWith('Gemfile')) {
14
+ if (HEALTH_DEPS.test(c)) hasHealthDep = true;
15
+ }
16
+ const endpoints = c.match(HEALTH_ENDPOINT);
17
+ if (endpoints) healthEndpointCount += endpoints.length;
18
+ }
19
+ // Need strong signal: health dependency OR 3+ health-related endpoints
20
+ return hasHealthDep || healthEndpointCount >= 3;
21
+ }
22
+
23
+ const rules = [
24
+ // COMP-HIPAA-007: PHI in error messages/stack traces
25
+ {
26
+ id: 'COMP-HIPAA-007', category: 'compliance', severity: 'critical', confidence: 'likely',
27
+ title: 'PHI Exposed in Error Messages',
28
+ check({ files }) {
29
+ const findings = [];
30
+ for (const [fp, c] of files) {
31
+ if (!isSourceFile(fp)) continue;
32
+ const lines = c.split('\n');
33
+ for (let i = 0; i < lines.length; i++) {
34
+ const line = lines[i];
35
+ if (/catch\s*\(/.test(line) || /\.catch\(/.test(line) || /error/i.test(line)) {
36
+ const block = lines.slice(i, Math.min(i + 10, lines.length)).join('\n');
37
+ if (PHI_FIELDS.test(block) && /res\.(json|send|status|write)|response\.(json|send)/.test(block)) {
38
+ findings.push({ ruleId: 'COMP-HIPAA-007', category: 'compliance', severity: 'critical',
39
+ title: 'PHI may be exposed in error response to client', description: 'Error handlers should never include PHI in responses. Log errors server-side only and return generic error messages to clients. HIPAA §164.312(a)(1).',
40
+ file: fp, line: i + 1, fix: null });
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ }
46
+ return findings;
47
+ },
48
+ },
49
+
50
+ // COMP-HIPAA-008: Missing BAA references
51
+ {
52
+ id: 'COMP-HIPAA-008', category: 'compliance', severity: 'high', confidence: 'likely',
53
+ title: 'Missing Business Associate Agreement (BAA) Reference',
54
+ check({ files }) {
55
+ const findings = [];
56
+ if (!hasHealthContext(files)) return findings;
57
+ const hasBAA = [...files.values()].some(c => /baa|business.associate|hipaa.agreement|covered.entity/i.test(c)) ||
58
+ [...files.keys()].some(f => /baa|hipaa/i.test(f));
59
+ if (!hasBAA) {
60
+ findings.push({ ruleId: 'COMP-HIPAA-008', category: 'compliance', severity: 'high',
61
+ title: 'Health app with no Business Associate Agreement (BAA) reference',
62
+ description: 'HIPAA requires BAAs with all third-party vendors who handle PHI. Document BAA requirements in your codebase or configuration.',
63
+ fix: null });
64
+ }
65
+ return findings;
66
+ },
67
+ },
68
+
69
+ // COMP-HIPAA-009: PHI shared with third-party SDKs
70
+ {
71
+ id: 'COMP-HIPAA-009', category: 'compliance', severity: 'critical', confidence: 'likely',
72
+ title: 'PHI Shared with Third-Party Monitoring SDKs',
73
+ check({ files }) {
74
+ const findings = [];
75
+ const thirdParty = /(?:Sentry|Datadog|NewRelic|Bugsnag|LogRocket|Rollbar|Raygun|TrackJS|datadog|sentry|newrelic|bugsnag|logrocket)/;
76
+ for (const [fp, c] of files) {
77
+ if (!isSourceFile(fp)) continue;
78
+ if (thirdParty.test(c) && PHI_FIELDS.test(c)) {
79
+ findings.push({ ruleId: 'COMP-HIPAA-009', category: 'compliance', severity: 'critical',
80
+ title: 'PHI may be sent to third-party monitoring service without BAA',
81
+ description: 'Ensure PHI is scrubbed before sending to third-party services. Each vendor handling PHI requires a signed BAA. Use beforeSend/beforeBreadcrumb hooks to filter PHI.',
82
+ file: fp, fix: null });
83
+ }
84
+ }
85
+ return findings;
86
+ },
87
+ },
88
+
89
+ // COMP-HIPAA-010: Missing session timeout for PHI access
90
+ {
91
+ id: 'COMP-HIPAA-010', category: 'compliance', severity: 'high', confidence: 'likely',
92
+ title: 'Missing Session Timeout for PHI Access',
93
+ check({ files }) {
94
+ const findings = [];
95
+ if (!hasHealthContext(files)) return findings;
96
+ const hasTimeout = [...files.values()].some(c =>
97
+ /session.*timeout|sessionTimeout|session.*expir|idle.*timeout|maxAge|cookie.*maxAge/i.test(c));
98
+ if (!hasTimeout) {
99
+ findings.push({ ruleId: 'COMP-HIPAA-010', category: 'compliance', severity: 'high',
100
+ title: 'No session timeout configured for health application',
101
+ description: 'HIPAA requires automatic logoff after periods of inactivity when accessing PHI. Implement session timeouts (recommended: 15 minutes). §164.312(a)(2)(iii).',
102
+ fix: null });
103
+ }
104
+ return findings;
105
+ },
106
+ },
107
+
108
+ // COMP-HIPAA-011: PHI in client-side storage
109
+ {
110
+ id: 'COMP-HIPAA-011', category: 'compliance', severity: 'critical', confidence: 'definite',
111
+ title: 'PHI in Client-Side Storage',
112
+ check({ files }) {
113
+ const findings = [];
114
+ for (const [fp, c] of files) {
115
+ if (!isSourceFile(fp)) continue;
116
+ const lines = c.split('\n');
117
+ for (let i = 0; i < lines.length; i++) {
118
+ const line = lines[i];
119
+ if (/localStorage|sessionStorage|document\.cookie/.test(line) && PHI_FIELDS.test(line)) {
120
+ findings.push({ ruleId: 'COMP-HIPAA-011', category: 'compliance', severity: 'critical',
121
+ title: 'PHI stored in browser storage (localStorage/sessionStorage/cookie)',
122
+ description: 'Browser storage is accessible to JavaScript and persists across sessions. Never store PHI client-side. Use server-side sessions with encrypted storage.',
123
+ file: fp, line: i + 1, fix: null });
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ return findings;
129
+ },
130
+ },
131
+
132
+ // COMP-HIPAA-012: Missing access controls on health endpoints
133
+ {
134
+ id: 'COMP-HIPAA-012', category: 'compliance', severity: 'critical', confidence: 'likely',
135
+ title: 'Missing Access Controls on Health Endpoints',
136
+ check({ files }) {
137
+ const findings = [];
138
+ for (const [fp, c] of files) {
139
+ if (!isSourceFile(fp)) continue;
140
+ if (!HEALTH_ENDPOINT.test(c)) continue;
141
+ const lines = c.split('\n');
142
+ for (let i = 0; i < lines.length; i++) {
143
+ if (HEALTH_ENDPOINT.test(lines[i]) && /\.(get|post|put|patch|delete)\s*\(/.test(lines[i])) {
144
+ const block = lines.slice(Math.max(0, i - 3), i + 3).join('\n');
145
+ if (!/auth|protect|authenticate|middleware|guard|requireAuth|isAuth|verifyToken|passport|jwt/i.test(block)) {
146
+ findings.push({ ruleId: 'COMP-HIPAA-012', category: 'compliance', severity: 'critical',
147
+ title: 'Health endpoint without authentication middleware',
148
+ description: 'All endpoints handling PHI must require authentication. HIPAA §164.312(d) requires person or entity authentication for PHI access.',
149
+ file: fp, line: i + 1, fix: null });
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return findings;
156
+ },
157
+ },
158
+
159
+ // COMP-HIPAA-013: PHI in email/SMS notifications
160
+ {
161
+ id: 'COMP-HIPAA-013', category: 'compliance', severity: 'high', confidence: 'likely',
162
+ title: 'PHI in Email/SMS Notifications',
163
+ check({ files }) {
164
+ const findings = [];
165
+ for (const [fp, c] of files) {
166
+ if (!isSourceFile(fp)) continue;
167
+ if (/sendEmail|sendMail|sendSMS|twilio|sendgrid|nodemailer|ses\.send|sns\.publish/i.test(c) && PHI_FIELDS.test(c)) {
168
+ const lines = c.split('\n');
169
+ for (let i = 0; i < lines.length; i++) {
170
+ if (/sendEmail|sendMail|sendSMS|twilio|sendgrid|nodemailer/i.test(lines[i])) {
171
+ const block = lines.slice(i, Math.min(i + 15, lines.length)).join('\n');
172
+ if (PHI_FIELDS.test(block)) {
173
+ findings.push({ ruleId: 'COMP-HIPAA-013', category: 'compliance', severity: 'high',
174
+ title: 'PHI may be included in email/SMS notifications',
175
+ description: 'Email and SMS are not secure channels for PHI. Use secure messaging portals with patient authentication instead of including health details in notifications.',
176
+ file: fp, line: i + 1, fix: null });
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ return findings;
184
+ },
185
+ },
186
+
187
+ // COMP-HIPAA-014: Missing backup/disaster recovery config
188
+ {
189
+ id: 'COMP-HIPAA-014', category: 'compliance', severity: 'high', confidence: 'likely',
190
+ title: 'Missing Backup/Disaster Recovery Configuration',
191
+ check({ files }) {
192
+ const findings = [];
193
+ if (!hasHealthContext(files)) return findings;
194
+ const hasBackup = [...files.values()].some(c => /backup|disaster.*recover|recovery.*plan|data.*replication|failover/i.test(c)) ||
195
+ [...files.keys()].some(f => /backup|disaster|recovery/i.test(f));
196
+ if (!hasBackup) {
197
+ findings.push({ ruleId: 'COMP-HIPAA-014', category: 'compliance', severity: 'high',
198
+ title: 'No backup or disaster recovery configuration for health application',
199
+ description: 'HIPAA §164.308(a)(7) requires contingency planning including data backup, disaster recovery, and emergency mode operation plans.',
200
+ fix: null });
201
+ }
202
+ return findings;
203
+ },
204
+ },
205
+
206
+ // COMP-HIPAA-015: PHI database without field-level encryption
207
+ {
208
+ id: 'COMP-HIPAA-015', category: 'compliance', severity: 'high', confidence: 'likely',
209
+ title: 'PHI Database Fields Without Encryption',
210
+ check({ files }) {
211
+ const findings = [];
212
+ for (const [fp, c] of files) {
213
+ if (!isSourceFile(fp) && !fp.endsWith('.sql')) continue;
214
+ if (!/schema|model|migration|entity|table/i.test(fp) && !/Schema|define|createTable|CREATE TABLE/i.test(c)) continue;
215
+ if (PHI_FIELDS.test(c) && !/encrypt|cipher|aes|pgcrypto|hashBytes/i.test(c)) {
216
+ findings.push({ ruleId: 'COMP-HIPAA-015', category: 'compliance', severity: 'high',
217
+ title: 'PHI fields in database schema without encryption references',
218
+ description: 'Consider field-level encryption for sensitive PHI fields. HIPAA requires encryption of ePHI at rest. Use database-level encryption (TDE) or application-level field encryption.',
219
+ file: fp, fix: null });
220
+ }
221
+ }
222
+ return findings;
223
+ },
224
+ },
225
+
226
+ // COMP-HIPAA-016: SELECT * on patient tables
227
+ {
228
+ id: 'COMP-HIPAA-016', category: 'compliance', severity: 'medium', confidence: 'likely',
229
+ title: 'SELECT * on Patient/Health Tables (Minimum Necessary Violation)',
230
+ check({ files }) {
231
+ const findings = [];
232
+ const selectStar = /SELECT\s+\*\s+FROM\s+(?:patient|health|medical|clinical|diagnosis|medication|prescription)/i;
233
+ for (const [fp, c] of files) {
234
+ if (!isSourceFile(fp) && !fp.endsWith('.sql')) continue;
235
+ const lines = c.split('\n');
236
+ for (let i = 0; i < lines.length; i++) {
237
+ if (selectStar.test(lines[i])) {
238
+ findings.push({ ruleId: 'COMP-HIPAA-016', category: 'compliance', severity: 'medium',
239
+ title: 'SELECT * from health/patient table violates minimum necessary principle',
240
+ description: 'HIPAA minimum necessary standard requires limiting PHI access to only what is needed. Select specific columns instead of SELECT *.',
241
+ file: fp, line: i + 1, fix: null });
242
+ }
243
+ }
244
+ }
245
+ return findings;
246
+ },
247
+ },
248
+
249
+ // COMP-HIPAA-017: Missing patient consent tracking
250
+ {
251
+ id: 'COMP-HIPAA-017', category: 'compliance', severity: 'high', confidence: 'likely',
252
+ title: 'Missing Patient Consent Tracking',
253
+ check({ files }) {
254
+ const findings = [];
255
+ if (!hasHealthContext(files)) return findings;
256
+ const hasConsent = [...files.values()].some(c => /consent|authorization|hipaa.*consent|patient.*consent|consent.*form/i.test(c)) ||
257
+ [...files.keys()].some(f => /consent/i.test(f));
258
+ if (!hasConsent) {
259
+ findings.push({ ruleId: 'COMP-HIPAA-017', category: 'compliance', severity: 'high',
260
+ title: 'No patient consent tracking mechanism found',
261
+ description: 'HIPAA requires tracking patient authorizations for use and disclosure of PHI. Implement consent management for data sharing beyond treatment, payment, and operations.',
262
+ fix: null });
263
+ }
264
+ return findings;
265
+ },
266
+ },
267
+
268
+ // COMP-HIPAA-018: PHI in cache without encryption
269
+ {
270
+ id: 'COMP-HIPAA-018', category: 'compliance', severity: 'high', confidence: 'likely',
271
+ title: 'PHI in Cache Without Encryption',
272
+ check({ files }) {
273
+ const findings = [];
274
+ for (const [fp, c] of files) {
275
+ if (!isSourceFile(fp)) continue;
276
+ if (/redis|memcached|cache\.set|cache\.put|cacheManager/i.test(c) && PHI_FIELDS.test(c)) {
277
+ if (!/encrypt|cipher|tls|ssl/i.test(c)) {
278
+ findings.push({ ruleId: 'COMP-HIPAA-018', category: 'compliance', severity: 'high',
279
+ title: 'PHI stored in cache (Redis/Memcached) potentially without encryption',
280
+ description: 'Cache systems storing PHI must use encryption in transit (TLS) and at rest. Configure Redis with TLS and consider encrypting values at the application level.',
281
+ file: fp, fix: null });
282
+ }
283
+ }
284
+ }
285
+ return findings;
286
+ },
287
+ },
288
+
289
+ // COMP-HIPAA-019: PHI retained beyond retention policy
290
+ {
291
+ id: 'COMP-HIPAA-019', category: 'compliance', severity: 'medium', confidence: 'likely',
292
+ title: 'No Data Retention Policy for PHI',
293
+ check({ files }) {
294
+ const findings = [];
295
+ if (!hasHealthContext(files)) return findings;
296
+ const hasRetention = [...files.values()].some(c => /retention|ttl|expir|purge|archive|cleanup.*patient|delete.*old/i.test(c)) ||
297
+ [...files.keys()].some(f => /retention|archive|cleanup/i.test(f));
298
+ if (!hasRetention) {
299
+ findings.push({ ruleId: 'COMP-HIPAA-019', category: 'compliance', severity: 'medium',
300
+ title: 'No data retention/expiration policy for PHI',
301
+ description: 'HIPAA requires retaining records for 6 years from creation or last effective date. Implement data retention policies with automated archival/deletion.',
302
+ fix: null });
303
+ }
304
+ return findings;
305
+ },
306
+ },
307
+
308
+ // COMP-HIPAA-020: Missing emergency access procedure
309
+ {
310
+ id: 'COMP-HIPAA-020', category: 'compliance', severity: 'medium', confidence: 'likely',
311
+ title: 'Missing Emergency Access Procedure',
312
+ check({ files }) {
313
+ const findings = [];
314
+ if (!hasHealthContext(files)) return findings;
315
+ const hasEmergency = [...files.values()].some(c => /emergency.*access|break.*glass|breakglass|emergency.*override|crisis.*mode/i.test(c));
316
+ if (!hasEmergency) {
317
+ findings.push({ ruleId: 'COMP-HIPAA-020', category: 'compliance', severity: 'medium',
318
+ title: 'No emergency access procedure for health application',
319
+ description: 'HIPAA §164.312(a)(2)(ii) requires emergency access procedures to obtain necessary ePHI during emergencies. Implement break-glass access with full audit logging.',
320
+ fix: null });
321
+ }
322
+ return findings;
323
+ },
324
+ },
325
+
326
+ // COMP-HIPAA-021: Health API without rate limiting
327
+ {
328
+ id: 'COMP-HIPAA-021', category: 'compliance', severity: 'medium', confidence: 'likely',
329
+ title: 'Health API Without Rate Limiting',
330
+ check({ files }) {
331
+ const findings = [];
332
+ const hasHealthAPI = [...files.values()].some(c => HEALTH_ENDPOINT.test(c) && /\.(get|post|put|delete)\s*\(/.test(c));
333
+ if (!hasHealthAPI) return findings;
334
+ const hasRateLimit = [...files.values()].some(c => /rateLimit|rate.limit|throttle|express-rate-limit|bottleneck/i.test(c));
335
+ if (!hasRateLimit) {
336
+ findings.push({ ruleId: 'COMP-HIPAA-021', category: 'compliance', severity: 'medium',
337
+ title: 'Health API endpoints without rate limiting',
338
+ description: 'Rate limiting protects against brute-force attacks on PHI endpoints. Implement rate limiting to prevent unauthorized bulk data access.',
339
+ fix: null });
340
+ }
341
+ return findings;
342
+ },
343
+ },
344
+
345
+ // COMP-HIPAA-022: PHI in mobile app without device encryption check
346
+ {
347
+ id: 'COMP-HIPAA-022', category: 'compliance', severity: 'high', confidence: 'likely',
348
+ title: 'Mobile Health App Without Device Encryption Check',
349
+ check({ files }) {
350
+ const findings = [];
351
+ const isMobile = [...files.keys()].some(f => /\.swift$|\.kt$|\.dart$|react-native|expo/i.test(f)) ||
352
+ [...files.values()].some(c => /react-native|flutter|expo|UIKit|SwiftUI|Jetpack/i.test(c));
353
+ if (!isMobile) return findings;
354
+ if (!hasHealthContext(files)) return findings;
355
+ const hasDeviceEncryption = [...files.values()].some(c => /device.*encrypt|isDeviceSecure|passcode|biometric|keychain|keystore|SecureEnclave/i.test(c));
356
+ if (!hasDeviceEncryption) {
357
+ findings.push({ ruleId: 'COMP-HIPAA-022', category: 'compliance', severity: 'high',
358
+ title: 'Mobile health app without device encryption verification',
359
+ description: 'Mobile apps handling PHI should verify device encryption is enabled before storing or displaying PHI. Use platform APIs to check device security state.',
360
+ fix: null });
361
+ }
362
+ return findings;
363
+ },
364
+ },
365
+
366
+ // COMP-HIPAA-023: Missing breach notification mechanism
367
+ {
368
+ id: 'COMP-HIPAA-023', category: 'compliance', severity: 'high', confidence: 'likely',
369
+ title: 'Missing Breach Notification Mechanism',
370
+ check({ files }) {
371
+ const findings = [];
372
+ if (!hasHealthContext(files)) return findings;
373
+ const hasBreach = [...files.values()].some(c => /breach|incident.*report|security.*incident|notification.*breach|data.*breach/i.test(c)) ||
374
+ [...files.keys()].some(f => /breach|incident/i.test(f));
375
+ if (!hasBreach) {
376
+ findings.push({ ruleId: 'COMP-HIPAA-023', category: 'compliance', severity: 'high',
377
+ title: 'No breach notification mechanism in health application',
378
+ description: 'HIPAA Breach Notification Rule requires notifying affected individuals within 60 days. Implement breach detection, logging, and notification workflows.',
379
+ fix: null });
380
+ }
381
+ return findings;
382
+ },
383
+ },
384
+
385
+ // COMP-HIPAA-024: Telehealth without end-to-end encryption
386
+ {
387
+ id: 'COMP-HIPAA-024', category: 'compliance', severity: 'critical', confidence: 'likely',
388
+ title: 'Telehealth Without End-to-End Encryption',
389
+ check({ files }) {
390
+ const findings = [];
391
+ const hasTelehealth = [...files.values()].some(c => /telehealth|telemedicine|video.*call|video.*consult|webrtc|peer.*connection/i.test(c));
392
+ if (!hasTelehealth) return findings;
393
+ const hasE2E = [...files.values()].some(c => /e2e|end.to.end|encrypted.*video|srtp|dtls|encryptionKey/i.test(c));
394
+ if (!hasE2E) {
395
+ findings.push({ ruleId: 'COMP-HIPAA-024', category: 'compliance', severity: 'critical',
396
+ title: 'Telehealth functionality without end-to-end encryption',
397
+ description: 'Telehealth communications containing PHI must be encrypted end-to-end. Use SRTP/DTLS for WebRTC or verified encrypted video platforms with BAAs.',
398
+ fix: null });
399
+ }
400
+ return findings;
401
+ },
402
+ },
403
+
404
+ // COMP-HIPAA-025: PHI in test fixtures/seed data
405
+ {
406
+ id: 'COMP-HIPAA-025', category: 'compliance', severity: 'high', confidence: 'likely',
407
+ title: 'PHI in Test Fixtures or Seed Data',
408
+ check({ files }) {
409
+ const findings = [];
410
+ const realPHI = /(?:\d{3}-\d{2}-\d{4})|(?:patient.*john|patient.*jane|diagnosis.*diabetes|medication.*metformin)/i;
411
+ for (const [fp, c] of files) {
412
+ if (!/test|spec|fixture|seed|mock|fake|sample/i.test(fp)) continue;
413
+ if (realPHI.test(c) && PHI_FIELDS.test(c)) {
414
+ findings.push({ ruleId: 'COMP-HIPAA-025', category: 'compliance', severity: 'high',
415
+ title: 'Test/seed data may contain real PHI patterns',
416
+ description: 'Never use real patient data in test fixtures. Use synthetic/fake data generators. Even realistic-looking PHI in tests can be a compliance risk if it matches real records.',
417
+ file: fp, fix: null });
418
+ }
419
+ }
420
+ return findings;
421
+ },
422
+ },
423
+
424
+ // === HITECH Rules ===
425
+
426
+ // COMP-HITECH-001: No breach notification endpoint
427
+ {
428
+ id: 'COMP-HITECH-001', category: 'compliance', severity: 'high', confidence: 'likely',
429
+ title: 'No Breach Notification Endpoint (HITECH)',
430
+ check({ files }) {
431
+ const findings = [];
432
+ if (!hasHealthContext(files)) return findings;
433
+ const hasNotifyEndpoint = [...files.values()].some(c =>
434
+ /breach.*notif|notify.*breach|incident.*report.*endpoint|\/api\/.*breach/i.test(c));
435
+ if (!hasNotifyEndpoint) {
436
+ findings.push({ ruleId: 'COMP-HITECH-001', category: 'compliance', severity: 'high',
437
+ title: 'No breach notification endpoint or process',
438
+ description: 'HITECH Act strengthened breach notification requirements. Implement automated breach notification systems to notify HHS, affected individuals, and media (for breaches affecting 500+).',
439
+ fix: null });
440
+ }
441
+ return findings;
442
+ },
443
+ },
444
+
445
+ // COMP-HITECH-002: Missing encryption safe harbor
446
+ {
447
+ id: 'COMP-HITECH-002', category: 'compliance', severity: 'high', confidence: 'likely',
448
+ title: 'Missing Encryption Safe Harbor Documentation',
449
+ check({ files }) {
450
+ const findings = [];
451
+ if (!hasHealthContext(files)) return findings;
452
+ const hasEncryption = [...files.values()].some(c => /aes-256|AES_256|encrypt.*at.*rest|encryption.*standard|NIST.*approved/i.test(c));
453
+ if (!hasEncryption) {
454
+ findings.push({ ruleId: 'COMP-HITECH-002', category: 'compliance', severity: 'high',
455
+ title: 'No NIST-approved encryption documented for PHI',
456
+ description: 'HITECH provides safe harbor from breach notification if PHI is encrypted with NIST-approved standards. Use AES-256 for data at rest and TLS 1.2+ for data in transit.',
457
+ fix: null });
458
+ }
459
+ return findings;
460
+ },
461
+ },
462
+
463
+ // COMP-HITECH-003: No accounting of disclosures
464
+ {
465
+ id: 'COMP-HITECH-003', category: 'compliance', severity: 'medium', confidence: 'likely',
466
+ title: 'No Accounting of Disclosures',
467
+ check({ files }) {
468
+ const findings = [];
469
+ if (!hasHealthContext(files)) return findings;
470
+ const hasDisclosure = [...files.values()].some(c => /disclosure.*log|accounting.*disclosure|phi.*disclosure|data.*sharing.*log|access.*log.*phi/i.test(c));
471
+ if (!hasDisclosure) {
472
+ findings.push({ ruleId: 'COMP-HITECH-003', category: 'compliance', severity: 'medium',
473
+ title: 'No accounting of disclosures for PHI',
474
+ description: 'HITECH expanded the right to accounting of disclosures to include treatment, payment, and operations when using EHR. Track all PHI disclosures with date, recipient, and purpose.',
475
+ fix: null });
476
+ }
477
+ return findings;
478
+ },
479
+ },
480
+
481
+ // COMP-HITECH-004: EHR without interoperability standards
482
+ {
483
+ id: 'COMP-HITECH-004', category: 'compliance', severity: 'medium', confidence: 'likely',
484
+ title: 'EHR Data Without Interoperability Standards',
485
+ check({ files }) {
486
+ const findings = [];
487
+ const hasEHR = [...files.values()].some(c => /ehr|electronic.*health.*record|emr|electronic.*medical/i.test(c));
488
+ if (!hasEHR) return findings;
489
+ const hasStandards = [...files.values()].some(c => /fhir|hl7|cda|ccda|dicom|icd-10|snomed|loinc/i.test(c));
490
+ if (!hasStandards) {
491
+ findings.push({ ruleId: 'COMP-HITECH-004', category: 'compliance', severity: 'medium',
492
+ title: 'EHR system without health data interoperability standards',
493
+ description: 'HITECH promotes interoperability through standards like HL7 FHIR, CDA, and standard terminologies (ICD-10, SNOMED, LOINC). Implement FHIR APIs for data exchange.',
494
+ fix: null });
495
+ }
496
+ return findings;
497
+ },
498
+ },
499
+
500
+ // COMP-HITECH-005: Missing meaningful use audit trail
501
+ {
502
+ id: 'COMP-HITECH-005', category: 'compliance', severity: 'medium', confidence: 'likely',
503
+ title: 'Missing Meaningful Use Audit Trail',
504
+ check({ files }) {
505
+ const findings = [];
506
+ const hasEHR = [...files.values()].some(c => /ehr|electronic.*health.*record|emr/i.test(c));
507
+ if (!hasEHR) return findings;
508
+ const hasAudit = [...files.values()].some(c => /audit.*trail|audit.*log|access.*log|activity.*log|user.*action.*log/i.test(c));
509
+ if (!hasAudit) {
510
+ findings.push({ ruleId: 'COMP-HITECH-005', category: 'compliance', severity: 'medium',
511
+ title: 'EHR without comprehensive audit trail for meaningful use',
512
+ description: 'HITECH meaningful use requirements include audit logging of all EHR access and modifications. Log user, timestamp, action, and data accessed.',
513
+ fix: null });
514
+ }
515
+ return findings;
516
+ },
517
+ },
518
+ ];
519
+
520
+ export default rules;