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,903 @@
1
+ function isSourceFile(f) { return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go', '.java', '.cs', '.php'].some(e => f.endsWith(e)); }
2
+
3
+ // --- Shared helper functions for common compliance checks ---
4
+
5
+ function hasUserData(files) {
6
+ return [...files.values()].some(c =>
7
+ c.includes('email') || c.includes('password') || c.includes('phone') ||
8
+ c.includes('address') || c.includes('firstName') || c.includes('lastName') ||
9
+ c.includes('first_name') || c.includes('last_name') || c.includes('user_name') ||
10
+ c.includes('username') || c.includes('dateOfBirth') || c.includes('date_of_birth') ||
11
+ c.includes('personalData') || c.includes('personal_data') || c.includes('cpf') ||
12
+ c.includes('ssn') || c.includes('national_id')
13
+ );
14
+ }
15
+
16
+ function hasConsentMechanism(files) {
17
+ return [...files.values()].some(c =>
18
+ c.includes('consent') || c.includes('Consent') || c.includes('CONSENT') ||
19
+ c.includes('opt-in') || c.includes('optIn') || c.includes('opt_in') ||
20
+ c.includes('agreeToTerms') || c.includes('agree_to_terms') ||
21
+ c.includes('acceptTerms') || c.includes('accept_terms') ||
22
+ c.includes('consentimiento') || c.includes('consentimento')
23
+ );
24
+ }
25
+
26
+ function hasPrivacyPolicy(files) {
27
+ const hasPage = [...files.keys()].some(f =>
28
+ f.match(/privacy/i) || f.match(/privacypolicy/i) || f.match(/privacy-policy/i)
29
+ );
30
+ const hasLink = [...files.values()].some(c =>
31
+ c.includes('/privacy') || c.includes('privacy-policy') || c.includes('privacy policy') ||
32
+ c.includes('privacyPolicy') || c.includes('privacy_policy')
33
+ );
34
+ return hasPage || hasLink;
35
+ }
36
+
37
+ function hasDataDeletionEndpoint(files) {
38
+ return [...files.entries()].some(([f, c]) =>
39
+ isSourceFile(f) &&
40
+ (c.includes('delete') || c.includes('DELETE') || c.includes('destroy') || c.includes('erase') || c.includes('purge')) &&
41
+ (c.includes('user') || c.includes('account') || c.includes('profile') || c.includes('personal'))
42
+ );
43
+ }
44
+
45
+ function hasDataAccessEndpoint(files) {
46
+ return [...files.entries()].some(([f, c]) =>
47
+ isSourceFile(f) &&
48
+ (c.includes('export') || c.includes('download') || c.includes('portability') || c.includes('getMyData') || c.includes('get_my_data')) &&
49
+ (c.includes('user') || c.includes('account') || c.includes('personal') || c.includes('profile'))
50
+ );
51
+ }
52
+
53
+ function hasDataRetentionPolicy(files) {
54
+ return [...files.values()].some(c =>
55
+ c.includes('retention') || c.includes('ttl') || c.includes('TTL') ||
56
+ c.includes('expir') || c.includes('cleanup') || c.includes('purge') ||
57
+ c.includes('data_retention') || c.includes('dataRetention') ||
58
+ c.includes('retentionPeriod') || c.includes('retention_period')
59
+ );
60
+ }
61
+
62
+ function hasCrossBorderTransfer(files) {
63
+ return [...files.values()].some(c =>
64
+ c.includes('amazonaws.com') || c.includes('googleapis.com') ||
65
+ c.includes('azure.') || c.includes('cloudflare') ||
66
+ c.includes('sendgrid') || c.includes('twilio') ||
67
+ c.includes('stripe') || c.includes('firebase') ||
68
+ c.includes('analytics') || c.includes('sentry') ||
69
+ c.includes('segment') || c.includes('mixpanel')
70
+ );
71
+ }
72
+
73
+ function hasBreachNotification(files) {
74
+ return [...files.values()].some(c =>
75
+ c.includes('breach') || c.includes('incident') ||
76
+ c.includes('dataBreachNotif') || c.includes('data_breach') ||
77
+ c.includes('securityIncident') || c.includes('security_incident') ||
78
+ c.includes('notifyBreach') || c.includes('notify_breach') ||
79
+ c.includes('incidentResponse') || c.includes('incident_response')
80
+ );
81
+ }
82
+
83
+ function hasDPO(files) {
84
+ return [...files.values()].some(c =>
85
+ c.includes('dpo') || c.includes('DPO') ||
86
+ c.includes('dataProtectionOfficer') || c.includes('data_protection_officer') ||
87
+ c.includes('privacyOfficer') || c.includes('privacy_officer') ||
88
+ c.includes('encarregado')
89
+ );
90
+ }
91
+
92
+ function hasImpactAssessment(files) {
93
+ return [...files.values()].some(c =>
94
+ c.includes('dpia') || c.includes('DPIA') ||
95
+ c.includes('impactAssessment') || c.includes('impact_assessment') ||
96
+ c.includes('privacyImpact') || c.includes('privacy_impact') ||
97
+ c.includes('dataProtectionImpact') || c.includes('data_protection_impact')
98
+ );
99
+ }
100
+
101
+ function hasSensitiveData(files) {
102
+ return [...files.values()].some(c =>
103
+ c.includes('health') || c.includes('medical') || c.includes('biometric') ||
104
+ c.includes('genetic') || c.includes('racial') || c.includes('ethnic') ||
105
+ c.includes('religion') || c.includes('political') || c.includes('sexual') ||
106
+ c.includes('criminal') || c.includes('fingerprint') || c.includes('faceId') ||
107
+ c.includes('face_id')
108
+ );
109
+ }
110
+
111
+ function hasMinorDetection(files) {
112
+ // Only match if the project explicitly deals with minors (not just any mention of "age" or "child")
113
+ return [...files.entries()].some(([fp, c]) =>
114
+ (fp.endsWith('package.json') || fp.endsWith('.json')) &&
115
+ /coppa|kids.*app|children.*platform|parental.*consent|minor.*protect/i.test(c)
116
+ );
117
+ }
118
+
119
+ function hasParentalConsent(files) {
120
+ return [...files.values()].some(c =>
121
+ c.includes('parentalConsent') || c.includes('parental_consent') ||
122
+ c.includes('parentConsent') || c.includes('parent_consent') ||
123
+ c.includes('guardianConsent') || c.includes('guardian_consent') ||
124
+ c.includes('verifyParent') || c.includes('verify_parent') ||
125
+ c.includes('parentVerif') || c.includes('parent_verif')
126
+ );
127
+ }
128
+
129
+ function hasMarketingFeatures(files) {
130
+ return [...files.values()].some(c =>
131
+ c.includes('newsletter') || c.includes('marketing') || c.includes('promotional') ||
132
+ c.includes('subscribe') || c.includes('campaign') || c.includes('emailBlast') ||
133
+ c.includes('email_blast') || c.includes('pushNotif')
134
+ );
135
+ }
136
+
137
+ function hasOptOut(files) {
138
+ return [...files.values()].some(c =>
139
+ c.includes('opt-out') || c.includes('optOut') || c.includes('opt_out') ||
140
+ c.includes('unsubscribe') || c.includes('Unsubscribe') || c.includes('UNSUBSCRIBE') ||
141
+ c.includes('doNotTrack') || c.includes('do_not_track') ||
142
+ c.includes('removeFromList') || c.includes('remove_from_list')
143
+ );
144
+ }
145
+
146
+ function hasGovernmentId(files) {
147
+ return [...files.values()].some(c =>
148
+ c.includes('tfn') || c.includes('TFN') ||
149
+ c.includes('medicare') || c.includes('Medicare') ||
150
+ c.includes('taxFileNumber') || c.includes('tax_file_number') ||
151
+ c.includes('governmentId') || c.includes('government_id') ||
152
+ c.includes('nationalId') || c.includes('national_id') ||
153
+ c.includes('driverLicense') || c.includes('driver_license') ||
154
+ c.includes('passportNumber') || c.includes('passport_number')
155
+ );
156
+ }
157
+
158
+ function hasGrievanceMechanism(files) {
159
+ return [...files.values()].some(c =>
160
+ c.includes('grievance') || c.includes('complaint') || c.includes('Complaint') ||
161
+ c.includes('redressal') || c.includes('dispute') || c.includes('feedback') ||
162
+ c.includes('contactDpo') || c.includes('contact_dpo') ||
163
+ c.includes('grievanceOfficer') || c.includes('grievance_officer') ||
164
+ c.includes('complaintForm') || c.includes('complaint_form')
165
+ );
166
+ }
167
+
168
+ function hasThirdPartySharing(files) {
169
+ return [...files.values()].some(c =>
170
+ c.includes('thirdParty') || c.includes('third_party') || c.includes('third-party') ||
171
+ c.includes('shareWith') || c.includes('share_with') ||
172
+ c.includes('partnerApi') || c.includes('partner_api') ||
173
+ c.includes('externalService') || c.includes('external_service') ||
174
+ c.includes('vendorApi') || c.includes('vendor_api')
175
+ );
176
+ }
177
+
178
+ function hasPurposeSpecification(files) {
179
+ return [...files.values()].some(c =>
180
+ c.includes('purpose') || c.includes('Purpose') ||
181
+ c.includes('purposeOfUse') || c.includes('purpose_of_use') ||
182
+ c.includes('dataUsagePurpose') || c.includes('data_usage_purpose') ||
183
+ c.includes('collectionPurpose') || c.includes('collection_purpose') ||
184
+ c.includes('processingPurpose') || c.includes('processing_purpose')
185
+ );
186
+ }
187
+
188
+ function hasExplicitConsent(files) {
189
+ return [...files.values()].some(c =>
190
+ c.includes('explicitConsent') || c.includes('explicit_consent') ||
191
+ c.includes('expressConsent') || c.includes('express_consent') ||
192
+ c.includes('consentExplicit') || c.includes('consent_explicit') ||
193
+ c.includes('informedConsent') || c.includes('informed_consent')
194
+ );
195
+ }
196
+
197
+ function hasUserFacingApp(files) {
198
+ return [...files.values()].some(c =>
199
+ c.includes('signup') || c.includes('login') || c.includes('register') ||
200
+ c.includes('<form') || c.includes('createUser') || c.includes('create_user') ||
201
+ c.includes('signUp') || c.includes('sign_up') || c.includes('createAccount')
202
+ );
203
+ }
204
+
205
+ function hasHealthData(files) {
206
+ return [...files.values()].some(c =>
207
+ c.includes('health') || c.includes('Health') || c.includes('medical') ||
208
+ c.includes('Medical') || c.includes('diagnosis') || c.includes('patient') ||
209
+ c.includes('prescription') || c.includes('symptom') || c.includes('treatment')
210
+ );
211
+ }
212
+
213
+ function hasAdequacyCheck(files) {
214
+ return [...files.values()].some(c =>
215
+ c.includes('adequacy') || c.includes('standardContractualClause') ||
216
+ c.includes('standard_contractual_clause') || c.includes('scc') ||
217
+ c.includes('bindingCorporateRules') || c.includes('binding_corporate_rules') ||
218
+ c.includes('dataTransferAgreement') || c.includes('data_transfer_agreement') ||
219
+ c.includes('crossBorderCompliance') || c.includes('cross_border_compliance')
220
+ );
221
+ }
222
+
223
+ function hasLegalBasis(files) {
224
+ return [...files.values()].some(c =>
225
+ c.includes('legalBasis') || c.includes('legal_basis') ||
226
+ c.includes('lawfulBasis') || c.includes('lawful_basis') ||
227
+ c.includes('legitimateInterest') || c.includes('legitimate_interest') ||
228
+ c.includes('contractualNecessity') || c.includes('contractual_necessity') ||
229
+ c.includes('vitalInterest') || c.includes('vital_interest')
230
+ );
231
+ }
232
+
233
+
234
+ const rules = [
235
+ // ===== Brazil LGPD (Lei Geral de Protecao de Dados) =====
236
+
237
+ {
238
+ id: 'COMP-LGPD-001',
239
+ category: 'compliance',
240
+ severity: 'high',
241
+ confidence: 'likely',
242
+ title: 'LGPD: No Legal Basis for Processing',
243
+ check({ files }) {
244
+ const findings = [];
245
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
246
+ if (!hasLegalBasis(files) && !hasConsentMechanism(files)) {
247
+ findings.push({
248
+ ruleId: 'COMP-LGPD-001', category: 'compliance', severity: 'high',
249
+ title: 'Collecting personal data without documented legal basis',
250
+ description: 'Brazil\'s LGPD (Art. 7) requires a legal basis for all personal data processing. Implement consent collection or document another legal basis (contract, legal obligation, legitimate interest, etc.).',
251
+ fix: null,
252
+ });
253
+ }
254
+ return findings;
255
+ },
256
+ },
257
+
258
+ {
259
+ id: 'COMP-LGPD-002',
260
+ category: 'compliance',
261
+ severity: 'medium',
262
+ confidence: 'likely',
263
+ title: 'LGPD: Missing DPO / Encarregado Reference',
264
+ check({ files }) {
265
+ const findings = [];
266
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
267
+ if (!hasDPO(files)) {
268
+ findings.push({
269
+ ruleId: 'COMP-LGPD-002', category: 'compliance', severity: 'medium',
270
+ title: 'App processing personal data without DPO/encarregado reference',
271
+ description: 'LGPD (Art. 41) requires appointment of a Data Protection Officer (encarregado). Add DPO contact info or reference in your application.',
272
+ fix: null,
273
+ });
274
+ }
275
+ return findings;
276
+ },
277
+ },
278
+
279
+ {
280
+ id: 'COMP-LGPD-003',
281
+ category: 'compliance',
282
+ severity: 'high',
283
+ confidence: 'likely',
284
+ title: 'LGPD: No Consent Management',
285
+ check({ files }) {
286
+ const findings = [];
287
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
288
+ if (!hasConsentMechanism(files)) {
289
+ findings.push({
290
+ ruleId: 'COMP-LGPD-003', category: 'compliance', severity: 'high',
291
+ title: 'Personal data processing without consent collection or management',
292
+ description: 'LGPD (Art. 8) requires that consent be free, informed, and unambiguous. Implement a consent management mechanism that records and tracks user consent.',
293
+ fix: null,
294
+ });
295
+ }
296
+ return findings;
297
+ },
298
+ },
299
+
300
+ {
301
+ id: 'COMP-LGPD-004',
302
+ category: 'compliance',
303
+ severity: 'high',
304
+ confidence: 'likely',
305
+ title: 'LGPD: Cross-Border Transfer Without Adequacy',
306
+ check({ files }) {
307
+ const findings = [];
308
+ if (!hasUserData(files)) return findings;
309
+ if (hasCrossBorderTransfer(files) && !hasAdequacyCheck(files)) {
310
+ findings.push({
311
+ ruleId: 'COMP-LGPD-004', category: 'compliance', severity: 'high',
312
+ title: 'Data transferred to external services without adequacy check',
313
+ description: 'LGPD (Art. 33) restricts international data transfers to countries with adequate data protection or with specific safeguards (standard contractual clauses, binding corporate rules). Document your transfer basis.',
314
+ fix: null,
315
+ });
316
+ }
317
+ return findings;
318
+ },
319
+ },
320
+
321
+ {
322
+ id: 'COMP-LGPD-005',
323
+ category: 'compliance',
324
+ severity: 'high',
325
+ confidence: 'likely',
326
+ title: 'LGPD: No Data Subject Rights Endpoints',
327
+ check({ files }) {
328
+ const findings = [];
329
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
330
+ const hasAccess = hasDataAccessEndpoint(files);
331
+ const hasDeletion = hasDataDeletionEndpoint(files);
332
+ if (!hasAccess || !hasDeletion) {
333
+ findings.push({
334
+ ruleId: 'COMP-LGPD-005', category: 'compliance', severity: 'high',
335
+ title: 'No endpoints for data subject access, correction, or deletion requests',
336
+ description: 'LGPD (Art. 18) grants data subjects the right to access, correct, anonymize, block, or delete their data. Provide API endpoints or UI flows for these requests.',
337
+ fix: null,
338
+ });
339
+ }
340
+ return findings;
341
+ },
342
+ },
343
+
344
+ {
345
+ id: 'COMP-LGPD-006',
346
+ category: 'compliance',
347
+ severity: 'critical',
348
+ confidence: 'likely',
349
+ title: 'LGPD: Children\'s Data Without Parental Consent',
350
+ check({ files }) {
351
+ const findings = [];
352
+ if (hasMinorDetection(files) && !hasParentalConsent(files)) {
353
+ findings.push({
354
+ ruleId: 'COMP-LGPD-006', category: 'compliance', severity: 'critical',
355
+ title: 'Age/minor detection without parental consent flow',
356
+ description: 'LGPD (Art. 14) requires that processing of children\'s and adolescents\' personal data be done with specific and prominent consent from a parent or legal guardian. Implement a parental consent verification flow.',
357
+ fix: null,
358
+ });
359
+ }
360
+ return findings;
361
+ },
362
+ },
363
+
364
+ {
365
+ id: 'COMP-LGPD-007',
366
+ category: 'compliance',
367
+ severity: 'medium',
368
+ confidence: 'likely',
369
+ title: 'LGPD: No Data Impact Assessment',
370
+ check({ files }) {
371
+ const findings = [];
372
+ if (!hasUserData(files)) return findings;
373
+ if (hasSensitiveData(files) && !hasImpactAssessment(files)) {
374
+ findings.push({
375
+ ruleId: 'COMP-LGPD-007', category: 'compliance', severity: 'medium',
376
+ title: 'Processing sensitive data without DPIA/impact assessment reference',
377
+ description: 'LGPD (Art. 38) allows the ANPD to require a Data Protection Impact Assessment for processing that may generate risks to civil liberties. Conduct a DPIA when processing sensitive personal data.',
378
+ fix: null,
379
+ });
380
+ }
381
+ return findings;
382
+ },
383
+ },
384
+
385
+ {
386
+ id: 'COMP-LGPD-008',
387
+ category: 'compliance',
388
+ severity: 'high',
389
+ confidence: 'likely',
390
+ title: 'LGPD: Missing Data Breach Notification',
391
+ check({ files }) {
392
+ const findings = [];
393
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
394
+ if (!hasBreachNotification(files)) {
395
+ findings.push({
396
+ ruleId: 'COMP-LGPD-008', category: 'compliance', severity: 'high',
397
+ title: 'No breach/incident notification mechanism',
398
+ description: 'LGPD (Art. 48) requires the controller to communicate to the ANPD and affected data subjects any security incident that may result in risk or damage. Implement breach detection and notification procedures.',
399
+ fix: null,
400
+ });
401
+ }
402
+ return findings;
403
+ },
404
+ },
405
+
406
+ // ===== Canada PIPEDA =====
407
+
408
+ {
409
+ id: 'COMP-PIPEDA-001',
410
+ category: 'compliance',
411
+ severity: 'high',
412
+ confidence: 'likely',
413
+ title: 'PIPEDA: Collection Without Knowledge and Consent',
414
+ check({ files }) {
415
+ const findings = [];
416
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
417
+ if (!hasConsentMechanism(files) && !hasPrivacyPolicy(files)) {
418
+ findings.push({
419
+ ruleId: 'COMP-PIPEDA-001', category: 'compliance', severity: 'high',
420
+ title: 'Collecting personal information without informing users or obtaining consent',
421
+ description: 'PIPEDA Principle 3 requires knowledge and consent for the collection, use, or disclosure of personal information. Implement clear notice and consent mechanisms.',
422
+ fix: null,
423
+ });
424
+ }
425
+ return findings;
426
+ },
427
+ },
428
+
429
+ {
430
+ id: 'COMP-PIPEDA-002',
431
+ category: 'compliance',
432
+ severity: 'medium',
433
+ confidence: 'likely',
434
+ title: 'PIPEDA: Data Retained Beyond Purpose',
435
+ check({ files }) {
436
+ const findings = [];
437
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
438
+ if (!hasDataRetentionPolicy(files)) {
439
+ findings.push({
440
+ ruleId: 'COMP-PIPEDA-002', category: 'compliance', severity: 'medium',
441
+ title: 'No data retention or cleanup policy detected',
442
+ description: 'PIPEDA Principle 5 requires that personal information be retained only as long as necessary for the identified purposes. Implement data retention policies with TTL or scheduled cleanup.',
443
+ fix: null,
444
+ });
445
+ }
446
+ return findings;
447
+ },
448
+ },
449
+
450
+ {
451
+ id: 'COMP-PIPEDA-003',
452
+ category: 'compliance',
453
+ severity: 'high',
454
+ confidence: 'likely',
455
+ title: 'PIPEDA: No Individual Access Mechanism',
456
+ check({ files }) {
457
+ const findings = [];
458
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
459
+ if (!hasDataAccessEndpoint(files)) {
460
+ findings.push({
461
+ ruleId: 'COMP-PIPEDA-003', category: 'compliance', severity: 'high',
462
+ title: 'No endpoint for individuals to access their personal data',
463
+ description: 'PIPEDA Principle 9 gives individuals the right to access their personal information held by an organization. Provide a data access or export endpoint.',
464
+ fix: null,
465
+ });
466
+ }
467
+ return findings;
468
+ },
469
+ },
470
+
471
+ {
472
+ id: 'COMP-PIPEDA-004',
473
+ category: 'compliance',
474
+ severity: 'high',
475
+ confidence: 'likely',
476
+ title: 'PIPEDA: Cross-Border Transfer Without Protection',
477
+ check({ files }) {
478
+ const findings = [];
479
+ if (!hasUserData(files)) return findings;
480
+ if (hasCrossBorderTransfer(files) && !hasAdequacyCheck(files) && !hasPrivacyPolicy(files)) {
481
+ findings.push({
482
+ ruleId: 'COMP-PIPEDA-004', category: 'compliance', severity: 'high',
483
+ title: 'Data sent to external services without documented safeguards',
484
+ description: 'PIPEDA requires that transfers to third parties (including cross-border) maintain a comparable level of protection. Document your safeguards and notify individuals of foreign transfers in your privacy policy.',
485
+ fix: null,
486
+ });
487
+ }
488
+ return findings;
489
+ },
490
+ },
491
+
492
+ {
493
+ id: 'COMP-PIPEDA-005',
494
+ category: 'compliance',
495
+ severity: 'medium',
496
+ confidence: 'likely',
497
+ title: 'PIPEDA: No Accountability Officer',
498
+ check({ files }) {
499
+ const findings = [];
500
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
501
+ if (!hasDPO(files)) {
502
+ findings.push({
503
+ ruleId: 'COMP-PIPEDA-005', category: 'compliance', severity: 'medium',
504
+ title: 'No privacy officer or accountability designation found',
505
+ description: 'PIPEDA Principle 1 (Accountability) requires an organization to designate a person responsible for compliance. Designate a privacy officer and make their contact information available.',
506
+ fix: null,
507
+ });
508
+ }
509
+ return findings;
510
+ },
511
+ },
512
+
513
+ {
514
+ id: 'COMP-PIPEDA-006',
515
+ category: 'compliance',
516
+ severity: 'critical',
517
+ confidence: 'likely',
518
+ title: 'PIPEDA: Sensitive Data Without Explicit Consent',
519
+ check({ files }) {
520
+ const findings = [];
521
+ if (!hasUserData(files)) return findings;
522
+ if (hasSensitiveData(files) && !hasExplicitConsent(files)) {
523
+ const hasAnyConsent = hasConsentMechanism(files);
524
+ if (!hasAnyConsent) {
525
+ findings.push({
526
+ ruleId: 'COMP-PIPEDA-006', category: 'compliance', severity: 'critical',
527
+ title: 'Processing health/financial/biometric data without explicit consent',
528
+ description: 'PIPEDA requires express (explicit) consent for sensitive personal information such as health, financial, or biometric data. Implement an explicit consent flow for sensitive data categories.',
529
+ fix: null,
530
+ });
531
+ }
532
+ }
533
+ return findings;
534
+ },
535
+ },
536
+
537
+ // ===== Australia Privacy Act (APPs) =====
538
+
539
+ {
540
+ id: 'COMP-APL-001',
541
+ category: 'compliance',
542
+ severity: 'high',
543
+ confidence: 'likely',
544
+ title: 'Australian Privacy: No Privacy Policy for APP Entities',
545
+ check({ files }) {
546
+ const findings = [];
547
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
548
+ if (!hasPrivacyPolicy(files)) {
549
+ findings.push({
550
+ ruleId: 'COMP-APL-001', category: 'compliance', severity: 'high',
551
+ title: 'App collecting data without a privacy policy',
552
+ description: 'Australian Privacy Principle 1 (APP 1) requires APP entities to have a clearly expressed and up-to-date privacy policy about how they manage personal information. Add a privacy policy page or link.',
553
+ fix: null,
554
+ });
555
+ }
556
+ return findings;
557
+ },
558
+ },
559
+
560
+ {
561
+ id: 'COMP-APL-002',
562
+ category: 'compliance',
563
+ severity: 'high',
564
+ confidence: 'likely',
565
+ title: 'Australian Privacy: Cross-Border Disclosure Without Consent',
566
+ check({ files }) {
567
+ const findings = [];
568
+ if (!hasUserData(files)) return findings;
569
+ if (hasCrossBorderTransfer(files) && !hasConsentMechanism(files) && !hasAdequacyCheck(files)) {
570
+ findings.push({
571
+ ruleId: 'COMP-APL-002', category: 'compliance', severity: 'high',
572
+ title: 'Sending data overseas without user notification or safeguards',
573
+ description: 'APP 8 requires that before disclosing personal information to an overseas recipient, the entity must take reasonable steps to ensure the recipient complies with the APPs, or obtain consent after informing of risks.',
574
+ fix: null,
575
+ });
576
+ }
577
+ return findings;
578
+ },
579
+ },
580
+
581
+ {
582
+ id: 'COMP-APL-003',
583
+ category: 'compliance',
584
+ severity: 'medium',
585
+ confidence: 'likely',
586
+ title: 'Australian Privacy: Direct Marketing Without Opt-Out',
587
+ check({ files }) {
588
+ const findings = [];
589
+ if (hasMarketingFeatures(files) && !hasOptOut(files)) {
590
+ findings.push({
591
+ ruleId: 'COMP-APL-003', category: 'compliance', severity: 'medium',
592
+ title: 'Marketing or promotional features without unsubscribe/opt-out',
593
+ description: 'APP 7 restricts use/disclosure of personal information for direct marketing and requires a simple opt-out mechanism. Add an unsubscribe or opt-out mechanism for all marketing communications.',
594
+ fix: null,
595
+ });
596
+ }
597
+ return findings;
598
+ },
599
+ },
600
+
601
+ {
602
+ id: 'COMP-APL-004',
603
+ category: 'compliance',
604
+ severity: 'high',
605
+ confidence: 'likely',
606
+ title: 'Australian Privacy: No Data Breach Notification',
607
+ check({ files }) {
608
+ const findings = [];
609
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
610
+ if (!hasBreachNotification(files)) {
611
+ findings.push({
612
+ ruleId: 'COMP-APL-004', category: 'compliance', severity: 'high',
613
+ title: 'No notifiable data breach scheme compliance',
614
+ description: 'The Notifiable Data Breaches (NDB) scheme under Part IIIC of the Privacy Act requires entities to notify the OAIC and affected individuals of eligible data breaches. Implement breach detection and notification procedures.',
615
+ fix: null,
616
+ });
617
+ }
618
+ return findings;
619
+ },
620
+ },
621
+
622
+ {
623
+ id: 'COMP-APL-005',
624
+ category: 'compliance',
625
+ severity: 'high',
626
+ confidence: 'likely',
627
+ title: 'Australian Privacy: Government Identifiers Collected',
628
+ check({ files }) {
629
+ const findings = [];
630
+ if (hasGovernmentId(files)) {
631
+ // Check if there is a stated necessity or lawful purpose
632
+ const hasNecessity = [...files.values()].some(c =>
633
+ c.includes('verification') || c.includes('identity') || c.includes('kyc') ||
634
+ c.includes('KYC') || c.includes('identityVerif')
635
+ );
636
+ if (!hasNecessity) {
637
+ findings.push({
638
+ ruleId: 'COMP-APL-005', category: 'compliance', severity: 'high',
639
+ title: 'Storing government ID numbers (TFN, Medicare, etc.) without demonstrated necessity',
640
+ description: 'APP 9 restricts adoption, use, or disclosure of government-related identifiers. Only collect government identifiers when required by law or for identity verification purposes, and document the necessity.',
641
+ fix: null,
642
+ });
643
+ }
644
+ }
645
+ return findings;
646
+ },
647
+ },
648
+
649
+ {
650
+ id: 'COMP-APL-006',
651
+ category: 'compliance',
652
+ severity: 'high',
653
+ confidence: 'likely',
654
+ title: 'Australian Privacy: Health Info Without Extra Safeguards',
655
+ check({ files }) {
656
+ const findings = [];
657
+ if (hasHealthData(files) && !hasExplicitConsent(files)) {
658
+ const hasHealthSafeguard = [...files.values()].some(c =>
659
+ c.includes('healthConsent') || c.includes('health_consent') ||
660
+ c.includes('medicalConsent') || c.includes('medical_consent') ||
661
+ c.includes('sensitiveConsent') || c.includes('sensitive_consent')
662
+ );
663
+ if (!hasHealthSafeguard && !hasConsentMechanism(files)) {
664
+ findings.push({
665
+ ruleId: 'COMP-APL-006', category: 'compliance', severity: 'high',
666
+ title: 'Health data processing without additional protections',
667
+ description: 'Health information is classified as sensitive information under the Privacy Act and requires explicit consent (APP 3) and additional safeguards. Implement explicit consent for health data collection and enhanced security measures.',
668
+ fix: null,
669
+ });
670
+ }
671
+ }
672
+ return findings;
673
+ },
674
+ },
675
+
676
+ // ===== India DPDPA (Digital Personal Data Protection Act) =====
677
+
678
+ {
679
+ id: 'COMP-DPDPA-001',
680
+ category: 'compliance',
681
+ severity: 'high',
682
+ confidence: 'likely',
683
+ title: 'DPDPA: Processing Without Consent or Notice',
684
+ check({ files }) {
685
+ const findings = [];
686
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
687
+ if (!hasConsentMechanism(files) && !hasPrivacyPolicy(files)) {
688
+ findings.push({
689
+ ruleId: 'COMP-DPDPA-001', category: 'compliance', severity: 'high',
690
+ title: 'Collecting personal data without clear notice or consent',
691
+ description: 'India\'s DPDPA (Sec. 5-6) requires that personal data be processed only with the consent of the data principal, preceded by a clear and itemized notice. Implement a consent flow with an accompanying notice describing the data collected and its purpose.',
692
+ fix: null,
693
+ });
694
+ }
695
+ return findings;
696
+ },
697
+ },
698
+
699
+ {
700
+ id: 'COMP-DPDPA-002',
701
+ category: 'compliance',
702
+ severity: 'medium',
703
+ confidence: 'likely',
704
+ title: 'DPDPA: No Grievance Redressal Mechanism',
705
+ check({ files }) {
706
+ const findings = [];
707
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
708
+ if (!hasGrievanceMechanism(files)) {
709
+ findings.push({
710
+ ruleId: 'COMP-DPDPA-002', category: 'compliance', severity: 'medium',
711
+ title: 'No complaint or grievance mechanism for data principals',
712
+ description: 'DPDPA (Sec. 13) requires data fiduciaries to provide a readily available means for data principals to make grievances. Implement a grievance redressal mechanism or complaint form.',
713
+ fix: null,
714
+ });
715
+ }
716
+ return findings;
717
+ },
718
+ },
719
+
720
+ {
721
+ id: 'COMP-DPDPA-003',
722
+ category: 'compliance',
723
+ severity: 'critical',
724
+ confidence: 'likely',
725
+ title: 'DPDPA: Children\'s Data Without Verifiable Parental Consent',
726
+ check({ files }) {
727
+ const findings = [];
728
+ if (hasMinorDetection(files) && !hasParentalConsent(files)) {
729
+ findings.push({
730
+ ruleId: 'COMP-DPDPA-003', category: 'compliance', severity: 'critical',
731
+ title: 'Processing minor\'s data without verifiable parental consent',
732
+ description: 'DPDPA (Sec. 9) requires verifiable consent of a parent or lawful guardian before processing a child\'s personal data. Implement age verification and a parental consent flow.',
733
+ fix: null,
734
+ });
735
+ }
736
+ return findings;
737
+ },
738
+ },
739
+
740
+ {
741
+ id: 'COMP-DPDPA-004',
742
+ category: 'compliance',
743
+ severity: 'high',
744
+ confidence: 'likely',
745
+ title: 'DPDPA: No Data Principal Rights',
746
+ check({ files }) {
747
+ const findings = [];
748
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
749
+ const hasAccess = hasDataAccessEndpoint(files);
750
+ const hasDeletion = hasDataDeletionEndpoint(files);
751
+ if (!hasAccess || !hasDeletion) {
752
+ findings.push({
753
+ ruleId: 'COMP-DPDPA-004', category: 'compliance', severity: 'high',
754
+ title: 'No mechanism for data principal access, correction, or erasure',
755
+ description: 'DPDPA (Sec. 11-12) grants data principals the right to access, correct, and erase their personal data. Provide endpoints or UI flows for these rights.',
756
+ fix: null,
757
+ });
758
+ }
759
+ return findings;
760
+ },
761
+ },
762
+
763
+ {
764
+ id: 'COMP-DPDPA-005',
765
+ category: 'compliance',
766
+ severity: 'high',
767
+ confidence: 'likely',
768
+ title: 'DPDPA: Cross-Border Transfer to Non-Approved Country',
769
+ check({ files }) {
770
+ const findings = [];
771
+ if (!hasUserData(files)) return findings;
772
+ if (hasCrossBorderTransfer(files)) {
773
+ const hasGovApproval = [...files.values()].some(c =>
774
+ c.includes('approvedCountry') || c.includes('approved_country') ||
775
+ c.includes('whitelistedCountry') || c.includes('whitelisted_country') ||
776
+ c.includes('allowedJurisdiction') || c.includes('allowed_jurisdiction') ||
777
+ c.includes('transferApproval') || c.includes('transfer_approval')
778
+ );
779
+ if (!hasGovApproval && !hasAdequacyCheck(files)) {
780
+ findings.push({
781
+ ruleId: 'COMP-DPDPA-005', category: 'compliance', severity: 'high',
782
+ title: 'Data transfer without government approval reference for destination country',
783
+ description: 'DPDPA (Sec. 16) permits cross-border transfer of personal data only to countries notified by the Central Government. Document compliance with approved transfer destinations or obtain government approval.',
784
+ fix: null,
785
+ });
786
+ }
787
+ }
788
+ return findings;
789
+ },
790
+ },
791
+
792
+ // ===== Japan APPI (Act on the Protection of Personal Information) =====
793
+
794
+ {
795
+ id: 'COMP-APPI-001',
796
+ category: 'compliance',
797
+ severity: 'high',
798
+ confidence: 'likely',
799
+ title: 'APPI: No Purpose of Use Specification',
800
+ check({ files }) {
801
+ const findings = [];
802
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
803
+ if (!hasPurposeSpecification(files) && !hasPrivacyPolicy(files)) {
804
+ findings.push({
805
+ ruleId: 'COMP-APPI-001', category: 'compliance', severity: 'high',
806
+ title: 'Collecting data without stated purpose of use',
807
+ description: 'Japan\'s APPI (Art. 17-18) requires a business operator to specify the purpose of use of personal information as concretely as possible and to notify or publicize it. Clearly state the purpose of data collection.',
808
+ fix: null,
809
+ });
810
+ }
811
+ return findings;
812
+ },
813
+ },
814
+
815
+ {
816
+ id: 'COMP-APPI-002',
817
+ category: 'compliance',
818
+ severity: 'high',
819
+ confidence: 'likely',
820
+ title: 'APPI: Third-Party Provision Without Consent',
821
+ check({ files }) {
822
+ const findings = [];
823
+ if (!hasUserData(files)) return findings;
824
+ if (hasThirdPartySharing(files) && !hasConsentMechanism(files)) {
825
+ findings.push({
826
+ ruleId: 'COMP-APPI-002', category: 'compliance', severity: 'high',
827
+ title: 'Sharing data with third parties without obtaining user consent',
828
+ description: 'APPI (Art. 27) requires prior consent of the individual before providing personal data to a third party (with limited exceptions). Obtain consent before sharing data with third-party services.',
829
+ fix: null,
830
+ });
831
+ }
832
+ return findings;
833
+ },
834
+ },
835
+
836
+ {
837
+ id: 'COMP-APPI-003',
838
+ category: 'compliance',
839
+ severity: 'high',
840
+ confidence: 'likely',
841
+ title: 'APPI: Cross-Border Transfer Without Consent',
842
+ check({ files }) {
843
+ const findings = [];
844
+ if (!hasUserData(files)) return findings;
845
+ if (hasCrossBorderTransfer(files) && !hasConsentMechanism(files) && !hasAdequacyCheck(files)) {
846
+ findings.push({
847
+ ruleId: 'COMP-APPI-003', category: 'compliance', severity: 'high',
848
+ title: 'International data transfer without user agreement or adequacy basis',
849
+ description: 'APPI (Art. 28) restricts cross-border transfers of personal data unless the recipient is in a country with equivalent protections, the recipient has adequate safeguards, or the individual has consented. Implement appropriate safeguards.',
850
+ fix: null,
851
+ });
852
+ }
853
+ return findings;
854
+ },
855
+ },
856
+
857
+ {
858
+ id: 'COMP-APPI-004',
859
+ category: 'compliance',
860
+ severity: 'medium',
861
+ confidence: 'likely',
862
+ title: 'APPI: No Opt-Out Mechanism',
863
+ check({ files }) {
864
+ const findings = [];
865
+ if (!hasUserFacingApp(files) || !hasUserData(files)) return findings;
866
+ if (hasThirdPartySharing(files) && !hasOptOut(files)) {
867
+ findings.push({
868
+ ruleId: 'COMP-APPI-004', category: 'compliance', severity: 'medium',
869
+ title: 'No mechanism to opt out of data sharing with third parties',
870
+ description: 'APPI (Art. 27) provides an opt-out exception for third-party provision, but requires providing an opt-out mechanism and notifying the PPC. Implement an opt-out mechanism for data sharing.',
871
+ fix: null,
872
+ });
873
+ }
874
+ return findings;
875
+ },
876
+ },
877
+
878
+ {
879
+ id: 'COMP-APPI-005',
880
+ category: 'compliance',
881
+ severity: 'critical',
882
+ confidence: 'likely',
883
+ title: 'APPI: Sensitive Personal Info Without Explicit Consent',
884
+ check({ files }) {
885
+ const findings = [];
886
+ if (!hasUserData(files)) return findings;
887
+ if (hasSensitiveData(files) && !hasExplicitConsent(files)) {
888
+ const hasAnyConsent = hasConsentMechanism(files);
889
+ if (!hasAnyConsent) {
890
+ findings.push({
891
+ ruleId: 'COMP-APPI-005', category: 'compliance', severity: 'critical',
892
+ title: 'Processing special care-required personal info (race, creed, health, criminal record) without explicit consent',
893
+ description: 'APPI (Art. 20) prohibits acquiring special care-required personal information without the prior consent of the individual. Implement explicit consent for any sensitive data categories.',
894
+ fix: null,
895
+ });
896
+ }
897
+ }
898
+ return findings;
899
+ },
900
+ },
901
+ ];
902
+
903
+ export default rules;