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.
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/bin/doorman.js +444 -0
- package/package.json +74 -0
- package/src/ai-fixer.js +559 -0
- package/src/ast-scanner.js +434 -0
- package/src/auth.js +149 -0
- package/src/baseline.js +48 -0
- package/src/compliance.js +539 -0
- package/src/config.js +466 -0
- package/src/custom-rules.js +32 -0
- package/src/dashboard.js +202 -0
- package/src/detector.js +142 -0
- package/src/fix-engine.js +48 -0
- package/src/fix-registry-extra.js +95 -0
- package/src/fix-registry-go-rust.js +77 -0
- package/src/fix-registry-java-csharp.js +77 -0
- package/src/fix-registry-js.js +99 -0
- package/src/fix-registry-mcp-ai.js +57 -0
- package/src/fix-registry-python.js +87 -0
- package/src/fixer-ruby-php.js +608 -0
- package/src/fixer.js +2113 -0
- package/src/hooks.js +115 -0
- package/src/ignore.js +176 -0
- package/src/index.js +384 -0
- package/src/metrics.js +126 -0
- package/src/monorepo.js +65 -0
- package/src/presets.js +54 -0
- package/src/reporter.js +975 -0
- package/src/rule-worker.js +36 -0
- package/src/rules/ast-rules.js +756 -0
- package/src/rules/bugs/accessibility.js +235 -0
- package/src/rules/bugs/ai-codegen-fixable.js +172 -0
- package/src/rules/bugs/ai-codegen.js +365 -0
- package/src/rules/bugs/code-smell-bugs.js +247 -0
- package/src/rules/bugs/crypto-bugs.js +195 -0
- package/src/rules/bugs/docker-bugs.js +158 -0
- package/src/rules/bugs/general.js +361 -0
- package/src/rules/bugs/go-bugs.js +279 -0
- package/src/rules/bugs/index.js +73 -0
- package/src/rules/bugs/js-api.js +257 -0
- package/src/rules/bugs/js-array-object.js +210 -0
- package/src/rules/bugs/js-async-fixable.js +223 -0
- package/src/rules/bugs/js-async.js +211 -0
- package/src/rules/bugs/js-closure-scope.js +182 -0
- package/src/rules/bugs/js-database.js +203 -0
- package/src/rules/bugs/js-error-handling.js +148 -0
- package/src/rules/bugs/js-logic.js +261 -0
- package/src/rules/bugs/js-memory.js +214 -0
- package/src/rules/bugs/js-node.js +361 -0
- package/src/rules/bugs/js-react.js +373 -0
- package/src/rules/bugs/js-regex.js +200 -0
- package/src/rules/bugs/js-state.js +272 -0
- package/src/rules/bugs/js-type-coercion.js +318 -0
- package/src/rules/bugs/nextjs-bugs.js +242 -0
- package/src/rules/bugs/nextjs-fixable.js +120 -0
- package/src/rules/bugs/node-fixable.js +178 -0
- package/src/rules/bugs/python-advanced.js +245 -0
- package/src/rules/bugs/python-fixable.js +98 -0
- package/src/rules/bugs/python.js +284 -0
- package/src/rules/bugs/react-fixable.js +207 -0
- package/src/rules/bugs/ruby-bugs.js +182 -0
- package/src/rules/bugs/shell-bugs.js +181 -0
- package/src/rules/bugs/silent-failures.js +261 -0
- package/src/rules/bugs/ts-bugs.js +235 -0
- package/src/rules/bugs/unused-vars.js +65 -0
- package/src/rules/compliance/accessibility-ext.js +468 -0
- package/src/rules/compliance/education.js +322 -0
- package/src/rules/compliance/financial.js +421 -0
- package/src/rules/compliance/frameworks.js +507 -0
- package/src/rules/compliance/healthcare.js +520 -0
- package/src/rules/compliance/index.js +2714 -0
- package/src/rules/compliance/regional-eu.js +480 -0
- package/src/rules/compliance/regional-international.js +903 -0
- package/src/rules/cost/index.js +1993 -0
- package/src/rules/data/index.js +2503 -0
- package/src/rules/dependencies/index.js +1684 -0
- package/src/rules/deployment/index.js +2050 -0
- package/src/rules/index.js +71 -0
- package/src/rules/infrastructure/index.js +3048 -0
- package/src/rules/performance/index.js +3455 -0
- package/src/rules/quality/index.js +3175 -0
- package/src/rules/reliability/index.js +3040 -0
- package/src/rules/scope-rules.js +815 -0
- package/src/rules/security/ai-api.js +1177 -0
- package/src/rules/security/auth.js +1328 -0
- package/src/rules/security/cors.js +127 -0
- package/src/rules/security/crypto.js +527 -0
- package/src/rules/security/csharp.js +862 -0
- package/src/rules/security/csrf.js +193 -0
- package/src/rules/security/dart.js +835 -0
- package/src/rules/security/deserialization.js +291 -0
- package/src/rules/security/file-upload.js +187 -0
- package/src/rules/security/go.js +850 -0
- package/src/rules/security/headers.js +235 -0
- package/src/rules/security/index.js +65 -0
- package/src/rules/security/injection.js +1639 -0
- package/src/rules/security/mcp-server.js +71 -0
- package/src/rules/security/misconfiguration.js +660 -0
- package/src/rules/security/oauth-jwt.js +329 -0
- package/src/rules/security/path-traversal.js +295 -0
- package/src/rules/security/php.js +1054 -0
- package/src/rules/security/prototype-pollution.js +283 -0
- package/src/rules/security/rate-limiting.js +208 -0
- package/src/rules/security/ruby.js +1061 -0
- package/src/rules/security/rust.js +693 -0
- package/src/rules/security/secrets.js +747 -0
- package/src/rules/security/shell.js +647 -0
- package/src/rules/security/ssrf.js +298 -0
- package/src/rules/security/supply-chain-advanced.js +393 -0
- package/src/rules/security/supply-chain.js +734 -0
- package/src/rules/security/swift.js +835 -0
- package/src/rules/security/taint.js +27 -0
- package/src/rules/security/xss.js +520 -0
- package/src/scan-cache.js +71 -0
- package/src/scanner.js +710 -0
- package/src/scope-analyzer.js +685 -0
- package/src/share.js +88 -0
- package/src/taint.js +300 -0
- package/src/telemetry.js +183 -0
- package/src/tracer.js +190 -0
- package/src/upload.js +35 -0
- package/src/worker.js +31 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
function isSourceFile(f) { return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go', '.java', '.cs', '.php'].some(e => f.endsWith(e)); }
|
|
2
|
+
function isWebFile(f) { return ['.html', '.htm', '.jsx', '.tsx', '.vue', '.svelte'].some(e => f.endsWith(e)); }
|
|
3
|
+
|
|
4
|
+
const rules = [
|
|
5
|
+
// === ePrivacy Directive Rules ===
|
|
6
|
+
|
|
7
|
+
{ id: 'COMP-EPRIVACY-001', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
8
|
+
title: 'Tracking Pixels Without Consent',
|
|
9
|
+
check({ files }) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
for (const [fp, c] of files) {
|
|
12
|
+
if (!isWebFile(fp) && !isSourceFile(fp)) continue;
|
|
13
|
+
if (/tracking.*pixel|pixel\.gif|beacon|1x1.*img|pixel\.png|web.*beacon/i.test(c)) {
|
|
14
|
+
if (!/consent|gdpr|cookie.*accept/i.test(c)) {
|
|
15
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-001', category: 'compliance', severity: 'high',
|
|
16
|
+
title: 'Tracking pixel/beacon loaded without consent check',
|
|
17
|
+
description: 'ePrivacy Directive requires consent before deploying tracking pixels. Gate tracking pixels behind user consent.',
|
|
18
|
+
file: fp, fix: null });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return findings;
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
{ id: 'COMP-EPRIVACY-002', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
27
|
+
title: 'Device Fingerprinting Without Consent',
|
|
28
|
+
check({ files }) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
for (const [fp, c] of files) {
|
|
31
|
+
if (!isSourceFile(fp)) continue;
|
|
32
|
+
if (/canvas.*fingerprint|getImageData.*toDataURL|AudioContext.*createOscillator|webgl.*getParameter|navigator\.plugins/i.test(c)) {
|
|
33
|
+
if (!/consent/i.test(c)) {
|
|
34
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-002', category: 'compliance', severity: 'high',
|
|
35
|
+
title: 'Device fingerprinting detected without consent mechanism',
|
|
36
|
+
description: 'Canvas/WebGL/AudioContext fingerprinting requires user consent under ePrivacy Directive. Implement consent before collecting device fingerprints.',
|
|
37
|
+
file: fp, fix: null });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
{ id: 'COMP-EPRIVACY-003', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
46
|
+
title: 'Email Marketing Without Opt-In',
|
|
47
|
+
check({ files }) {
|
|
48
|
+
const findings = [];
|
|
49
|
+
for (const [fp, c] of files) {
|
|
50
|
+
if (!isSourceFile(fp)) continue;
|
|
51
|
+
if (/bulk.*email|mass.*mail|newsletter.*send|marketing.*email|email.*campaign|mailchimp|sendgrid.*marketing/i.test(c)) {
|
|
52
|
+
if (!/opt.in|subscribe|confirm.*email|double.*opt|verified.*consent/i.test(c)) {
|
|
53
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-003', category: 'compliance', severity: 'high',
|
|
54
|
+
title: 'Email marketing without opt-in verification',
|
|
55
|
+
description: 'ePrivacy Directive requires prior consent for marketing emails. Implement double opt-in for email marketing lists.',
|
|
56
|
+
file: fp, fix: null });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return findings;
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
{ id: 'COMP-EPRIVACY-004', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
65
|
+
title: 'Location Tracking Without Explicit Consent',
|
|
66
|
+
check({ files }) {
|
|
67
|
+
const findings = [];
|
|
68
|
+
for (const [fp, c] of files) {
|
|
69
|
+
if (!isSourceFile(fp)) continue;
|
|
70
|
+
const lines = c.split('\n');
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
if (/navigator\.geolocation|getCurrentPosition|watchPosition/i.test(lines[i])) {
|
|
73
|
+
const block = lines.slice(Math.max(0, i - 5), i + 5).join('\n');
|
|
74
|
+
if (!/consent|permission|allow.*location|ask.*location/i.test(block)) {
|
|
75
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-004', category: 'compliance', severity: 'high',
|
|
76
|
+
title: 'Geolocation API used without explicit consent flow',
|
|
77
|
+
description: 'Location data requires explicit user consent under ePrivacy. While browsers prompt for permission, apps should explain why location is needed before requesting.',
|
|
78
|
+
file: fp, line: i + 1, fix: null });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return findings;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
{ id: 'COMP-EPRIVACY-005', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
89
|
+
title: 'Push Notifications Without Consent',
|
|
90
|
+
check({ files }) {
|
|
91
|
+
const findings = [];
|
|
92
|
+
for (const [fp, c] of files) {
|
|
93
|
+
if (!isSourceFile(fp)) continue;
|
|
94
|
+
if (/pushManager\.subscribe|registerForRemoteNotifications|firebase.*messaging|FCM|push.*notification/i.test(c)) {
|
|
95
|
+
if (!/consent|opt.in|permission.*request|ask.*permission/i.test(c)) {
|
|
96
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-005', category: 'compliance', severity: 'medium',
|
|
97
|
+
title: 'Push notification registration without consent flow',
|
|
98
|
+
description: 'Implement a clear consent flow before registering for push notifications. Explain what notifications the user will receive.',
|
|
99
|
+
file: fp, fix: null });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return findings;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
{ id: 'COMP-EPRIVACY-006', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
108
|
+
title: 'Third-Party Trackers Loaded Before Consent',
|
|
109
|
+
check({ files }) {
|
|
110
|
+
const findings = [];
|
|
111
|
+
for (const [fp, c] of files) {
|
|
112
|
+
if (!isWebFile(fp)) continue;
|
|
113
|
+
if (/<head[\s>]/i.test(c)) {
|
|
114
|
+
const headMatch = c.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
115
|
+
if (headMatch) {
|
|
116
|
+
const head = headMatch[1];
|
|
117
|
+
if (/gtag|google-analytics|analytics\.js|facebook.*pixel|fbevents|hotjar|mixpanel|segment\.com/i.test(head)) {
|
|
118
|
+
if (!/consent|cookieConsent|cookie_consent/i.test(head)) {
|
|
119
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-006', category: 'compliance', severity: 'high',
|
|
120
|
+
title: 'Analytics/tracking scripts in <head> without consent gate',
|
|
121
|
+
description: 'Third-party tracking scripts must not load before user consent. Use a consent management platform to conditionally load scripts.',
|
|
122
|
+
file: fp, fix: null });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return findings;
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
{ id: 'COMP-EPRIVACY-007', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
133
|
+
title: 'Cookie Wall Blocking Content Access',
|
|
134
|
+
check({ files }) {
|
|
135
|
+
const findings = [];
|
|
136
|
+
for (const [fp, c] of files) {
|
|
137
|
+
if (!isSourceFile(fp) && !isWebFile(fp)) continue;
|
|
138
|
+
if (/cookie.*wall|block.*content.*consent|require.*consent.*access|no.*access.*without.*cookie/i.test(c)) {
|
|
139
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-007', category: 'compliance', severity: 'medium',
|
|
140
|
+
title: 'Cookie wall detected — blocking access until consent',
|
|
141
|
+
description: 'EDPB guidelines state cookie walls that deny access to content unless cookies are accepted do not constitute valid consent. Provide a genuine choice.',
|
|
142
|
+
file: fp, fix: null });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return findings;
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
{ id: 'COMP-EPRIVACY-008', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
150
|
+
title: 'Non-Essential Cookies Set on Page Load',
|
|
151
|
+
check({ files }) {
|
|
152
|
+
const findings = [];
|
|
153
|
+
for (const [fp, c] of files) {
|
|
154
|
+
if (!isSourceFile(fp)) continue;
|
|
155
|
+
if (/(?:componentDidMount|useEffect|mounted|onMounted|ngOnInit|DOMContentLoaded)/.test(c)) {
|
|
156
|
+
const lines = c.split('\n');
|
|
157
|
+
for (let i = 0; i < lines.length; i++) {
|
|
158
|
+
if (/(?:componentDidMount|useEffect|mounted|DOMContentLoaded)/.test(lines[i])) {
|
|
159
|
+
const block = lines.slice(i, Math.min(i + 15, lines.length)).join('\n');
|
|
160
|
+
if (/document\.cookie\s*=|setCookie|cookies\.set/i.test(block) && !/consent|essential|necessary|session/i.test(block)) {
|
|
161
|
+
findings.push({ ruleId: 'COMP-EPRIVACY-008', category: 'compliance', severity: 'high',
|
|
162
|
+
title: 'Non-essential cookies set during page initialization',
|
|
163
|
+
description: 'Cookies that are not strictly necessary must not be set before obtaining user consent. Defer non-essential cookie setting until after consent.',
|
|
164
|
+
file: fp, line: i + 1, fix: null });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return findings;
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// === Digital Services Act (DSA) Rules ===
|
|
176
|
+
|
|
177
|
+
{ id: 'COMP-DSA-001', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
178
|
+
title: 'No Content Moderation Mechanism',
|
|
179
|
+
check({ files }) {
|
|
180
|
+
const findings = [];
|
|
181
|
+
const hasUGC = [...files.values()].some(c => /user.*content|comment|post|upload|review|forum|message.*board/i.test(c));
|
|
182
|
+
if (!hasUGC) return findings;
|
|
183
|
+
const hasModeration = [...files.values()].some(c => /moderate|moderation|report|flag|abuse|content.*review|takedown/i.test(c));
|
|
184
|
+
if (!hasModeration) {
|
|
185
|
+
findings.push({ ruleId: 'COMP-DSA-001', category: 'compliance', severity: 'high',
|
|
186
|
+
title: 'User-generated content platform without content moderation',
|
|
187
|
+
description: 'DSA requires platforms to implement content moderation mechanisms. Add reporting, flagging, and review workflows for user content.',
|
|
188
|
+
fix: null });
|
|
189
|
+
}
|
|
190
|
+
return findings;
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
{ id: 'COMP-DSA-002', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
195
|
+
title: 'Missing Algorithmic Transparency',
|
|
196
|
+
check({ files }) {
|
|
197
|
+
const findings = [];
|
|
198
|
+
const hasRecommendation = [...files.values()].some(c => /recommend|personali[sz]|feed.*algorithm|ranking.*algorithm|suggestion.*engine/i.test(c));
|
|
199
|
+
if (!hasRecommendation) return findings;
|
|
200
|
+
const hasTransparency = [...files.values()].some(c => /algorithm.*explain|transparency|why.*recommended|recommendation.*reason/i.test(c));
|
|
201
|
+
if (!hasTransparency) {
|
|
202
|
+
findings.push({ ruleId: 'COMP-DSA-002', category: 'compliance', severity: 'medium',
|
|
203
|
+
title: 'Recommendation algorithm without transparency mechanism',
|
|
204
|
+
description: 'DSA requires platforms to explain their recommendation algorithms to users. Provide clear information about why content is recommended.',
|
|
205
|
+
fix: null });
|
|
206
|
+
}
|
|
207
|
+
return findings;
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
{ id: 'COMP-DSA-003', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
212
|
+
title: 'No Illegal Content Reporting Endpoint',
|
|
213
|
+
check({ files }) {
|
|
214
|
+
const findings = [];
|
|
215
|
+
const hasPlatform = [...files.values()].some(c => /user.*content|comment|post|upload|marketplace|listing/i.test(c));
|
|
216
|
+
if (!hasPlatform) return findings;
|
|
217
|
+
const hasReporting = [...files.values()].some(c => /report.*illegal|illegal.*content|report.*abuse|notice.*action|law.*enforcement/i.test(c));
|
|
218
|
+
if (!hasReporting) {
|
|
219
|
+
findings.push({ ruleId: 'COMP-DSA-003', category: 'compliance', severity: 'high',
|
|
220
|
+
title: 'No mechanism for reporting illegal content',
|
|
221
|
+
description: 'DSA requires easy-to-access mechanisms for reporting illegal content. Implement a clear reporting flow with acknowledgment and response timelines.',
|
|
222
|
+
fix: null });
|
|
223
|
+
}
|
|
224
|
+
return findings;
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
{ id: 'COMP-DSA-004', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
229
|
+
title: 'Missing Notice-and-Action Procedure',
|
|
230
|
+
check({ files }) {
|
|
231
|
+
const findings = [];
|
|
232
|
+
const hasPlatform = [...files.values()].some(c => /user.*content|comment|post|upload/i.test(c));
|
|
233
|
+
if (!hasPlatform) return findings;
|
|
234
|
+
const hasNoticeAction = [...files.values()].some(c => /notice.*action|takedown|content.*removal|appeal|counter.*notice/i.test(c));
|
|
235
|
+
if (!hasNoticeAction) {
|
|
236
|
+
findings.push({ ruleId: 'COMP-DSA-004', category: 'compliance', severity: 'medium',
|
|
237
|
+
title: 'No notice-and-action procedure for content removal',
|
|
238
|
+
description: 'DSA requires structured notice-and-action procedures including appeals. Implement content removal workflows with user notification and appeal mechanisms.',
|
|
239
|
+
fix: null });
|
|
240
|
+
}
|
|
241
|
+
return findings;
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
{ id: 'COMP-DSA-005', category: 'compliance', severity: 'low', confidence: 'suggestion',
|
|
246
|
+
title: 'No Transparency Report Reference',
|
|
247
|
+
check({ files }) {
|
|
248
|
+
const findings = [];
|
|
249
|
+
const hasPlatform = [...files.values()].some(c => /user.*content|marketplace|platform/i.test(c));
|
|
250
|
+
if (!hasPlatform) return findings;
|
|
251
|
+
const hasTransparency = [...files.values()].some(c => /transparency.*report|annual.*report|moderation.*report|content.*statistics/i.test(c)) ||
|
|
252
|
+
[...files.keys()].some(f => /transparency/i.test(f));
|
|
253
|
+
if (!hasTransparency) {
|
|
254
|
+
findings.push({ ruleId: 'COMP-DSA-005', category: 'compliance', severity: 'low',
|
|
255
|
+
title: 'No transparency report reference for platform',
|
|
256
|
+
description: 'DSA requires annual transparency reports on content moderation. Plan for collecting and publishing moderation statistics.',
|
|
257
|
+
fix: null });
|
|
258
|
+
}
|
|
259
|
+
return findings;
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
// === EU AI Act Rules ===
|
|
264
|
+
|
|
265
|
+
{ id: 'COMP-EUAI-001', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
266
|
+
title: 'AI System Without Risk Classification',
|
|
267
|
+
check({ files }) {
|
|
268
|
+
const findings = [];
|
|
269
|
+
const hasAI = [...files.values()].some(c => /tensorflow|pytorch|scikit|openai|anthropic|model\.predict|ml.*pipeline|machine.*learning|neural.*network/i.test(c));
|
|
270
|
+
if (!hasAI) return findings;
|
|
271
|
+
const hasRisk = [...files.values()].some(c => /risk.*classif|risk.*level|risk.*assess|high.risk|low.risk|ai.*risk/i.test(c));
|
|
272
|
+
if (!hasRisk) {
|
|
273
|
+
findings.push({ ruleId: 'COMP-EUAI-001', category: 'compliance', severity: 'high',
|
|
274
|
+
title: 'AI system deployed without risk classification',
|
|
275
|
+
description: 'EU AI Act requires AI systems to be classified by risk level (unacceptable, high, limited, minimal). Document the risk classification of your AI system.',
|
|
276
|
+
fix: null });
|
|
277
|
+
}
|
|
278
|
+
return findings;
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
{ id: 'COMP-EUAI-002', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
283
|
+
title: 'No Human Oversight for AI Decisions',
|
|
284
|
+
check({ files }) {
|
|
285
|
+
const findings = [];
|
|
286
|
+
const hasAutoDecision = [...files.values()].some(c => /model\.predict|automat.*decision|ai.*decision|ml.*decision|classification.*result/i.test(c));
|
|
287
|
+
if (!hasAutoDecision) return findings;
|
|
288
|
+
const hasHumanOversight = [...files.values()].some(c => /human.*review|manual.*review|human.*oversight|human.*in.*loop|override|escalat/i.test(c));
|
|
289
|
+
if (!hasHumanOversight) {
|
|
290
|
+
findings.push({ ruleId: 'COMP-EUAI-002', category: 'compliance', severity: 'high',
|
|
291
|
+
title: 'Automated AI decisions without human oversight mechanism',
|
|
292
|
+
description: 'EU AI Act requires human oversight for high-risk AI systems. Implement human-in-the-loop review, override capabilities, and escalation procedures.',
|
|
293
|
+
fix: null });
|
|
294
|
+
}
|
|
295
|
+
return findings;
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
{ id: 'COMP-EUAI-003', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
300
|
+
title: 'AI Training Without Bias Documentation',
|
|
301
|
+
check({ files }) {
|
|
302
|
+
const findings = [];
|
|
303
|
+
const hasTraining = [...files.values()].some(c => /model\.fit|train.*model|training.*data|fine.*tun|dataset.*load/i.test(c));
|
|
304
|
+
if (!hasTraining) return findings;
|
|
305
|
+
const hasBiasCheck = [...files.values()].some(c => /bias|fairness|disparate.*impact|demographic.*parity|equalized.*odds|protected.*class/i.test(c));
|
|
306
|
+
if (!hasBiasCheck) {
|
|
307
|
+
findings.push({ ruleId: 'COMP-EUAI-003', category: 'compliance', severity: 'medium',
|
|
308
|
+
title: 'AI model training without bias/fairness assessment',
|
|
309
|
+
description: 'EU AI Act requires high-risk AI systems to be tested for bias. Implement fairness metrics and document bias assessment results for training data and model outputs.',
|
|
310
|
+
fix: null });
|
|
311
|
+
}
|
|
312
|
+
return findings;
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
{ id: 'COMP-EUAI-004', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
317
|
+
title: 'Missing AI System Transparency Disclosure',
|
|
318
|
+
check({ files }) {
|
|
319
|
+
const findings = [];
|
|
320
|
+
const hasAI = [...files.values()].some(c => /openai|anthropic|model\.predict|chatbot|ai.*assist|llm|gpt|claude/i.test(c));
|
|
321
|
+
if (!hasAI) return findings;
|
|
322
|
+
const hasDisclosure = [...files.values()].some(c => /ai.*disclosure|powered.*by.*ai|ai.*generated|artificial.*intelligence.*notice|ai.*transparency/i.test(c));
|
|
323
|
+
if (!hasDisclosure) {
|
|
324
|
+
findings.push({ ruleId: 'COMP-EUAI-004', category: 'compliance', severity: 'high',
|
|
325
|
+
title: 'AI features without user-facing transparency disclosure',
|
|
326
|
+
description: 'EU AI Act requires users to be informed when interacting with AI systems. Add clear disclosure that AI/ML is being used.',
|
|
327
|
+
fix: null });
|
|
328
|
+
}
|
|
329
|
+
return findings;
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
{ id: 'COMP-EUAI-005', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
334
|
+
title: 'Automated Decision Without Explanation Endpoint',
|
|
335
|
+
check({ files }) {
|
|
336
|
+
const findings = [];
|
|
337
|
+
const hasAutoDecision = [...files.values()].some(c => /model\.predict|automat.*decision|risk.*score|credit.*score|eligib/i.test(c));
|
|
338
|
+
if (!hasAutoDecision) return findings;
|
|
339
|
+
const hasExplain = [...files.values()].some(c => /explain|shap|lime|feature.*importance|decision.*reason|interpretab/i.test(c));
|
|
340
|
+
if (!hasExplain) {
|
|
341
|
+
findings.push({ ruleId: 'COMP-EUAI-005', category: 'compliance', severity: 'medium',
|
|
342
|
+
title: 'AI-powered decisions without explanation mechanism',
|
|
343
|
+
description: 'Provide explanations for automated decisions. Use explainability tools (SHAP, LIME) or provide feature importance to help users understand AI decisions.',
|
|
344
|
+
fix: null });
|
|
345
|
+
}
|
|
346
|
+
return findings;
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
{ id: 'COMP-EUAI-006', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
351
|
+
title: 'AI-Generated Content Without Disclosure',
|
|
352
|
+
check({ files }) {
|
|
353
|
+
const findings = [];
|
|
354
|
+
const hasGenAI = [...files.values()].some(c => /generate.*text|generate.*image|ai.*generat|completion|chat.*completion|dall-e|stable.*diffusion|midjourney/i.test(c));
|
|
355
|
+
if (!hasGenAI) return findings;
|
|
356
|
+
const hasLabel = [...files.values()].some(c => /ai.*generated|generated.*by.*ai|synthetic|machine.*generated|ai.*label|watermark/i.test(c));
|
|
357
|
+
if (!hasLabel) {
|
|
358
|
+
findings.push({ ruleId: 'COMP-EUAI-006', category: 'compliance', severity: 'medium',
|
|
359
|
+
title: 'AI-generated content without disclosure/labeling',
|
|
360
|
+
description: 'EU AI Act requires AI-generated content (text, images, audio, video) to be clearly labeled. Add AI-generated labels or watermarks to synthetic content.',
|
|
361
|
+
fix: null });
|
|
362
|
+
}
|
|
363
|
+
return findings;
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
{ id: 'COMP-EUAI-007', category: 'compliance', severity: 'critical', confidence: 'likely',
|
|
368
|
+
title: 'Emotion Recognition Without Safeguards',
|
|
369
|
+
check({ files }) {
|
|
370
|
+
const findings = [];
|
|
371
|
+
for (const [fp, c] of files) {
|
|
372
|
+
if (!isSourceFile(fp)) continue;
|
|
373
|
+
if (/emotion.*detect|facial.*expression|sentiment.*analy|affect.*recognition|emotion.*recognition|mood.*detect/i.test(c)) {
|
|
374
|
+
if (!/consent|opt.in|safeguard|disable|exempt/i.test(c)) {
|
|
375
|
+
findings.push({ ruleId: 'COMP-EUAI-007', category: 'compliance', severity: 'critical',
|
|
376
|
+
title: 'Emotion/sentiment recognition without consent and safeguards',
|
|
377
|
+
description: 'EU AI Act restricts emotion recognition in workplaces and education. Implement explicit consent, allow opt-out, and document the purpose and safeguards.',
|
|
378
|
+
file: fp, fix: null });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return findings;
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// === NIS2 Directive Rules ===
|
|
387
|
+
|
|
388
|
+
{ id: 'COMP-NIS2-001', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
389
|
+
title: 'Missing Incident Reporting Mechanism',
|
|
390
|
+
check({ files }) {
|
|
391
|
+
const findings = [];
|
|
392
|
+
const hasApp = [...files.keys()].some(f => /package\.json|requirements\.txt|go\.mod|Gemfile/i.test(f));
|
|
393
|
+
if (!hasApp) return findings;
|
|
394
|
+
const hasIncident = [...files.values()].some(c => /incident.*report|security.*incident|incident.*response|alert.*security|pagerduty|opsgenie/i.test(c)) ||
|
|
395
|
+
[...files.keys()].some(f => /incident/i.test(f));
|
|
396
|
+
if (!hasIncident) {
|
|
397
|
+
findings.push({ ruleId: 'COMP-NIS2-001', category: 'compliance', severity: 'high',
|
|
398
|
+
title: 'No security incident reporting mechanism',
|
|
399
|
+
description: 'NIS2 requires significant incidents to be reported within 24 hours. Implement incident detection, classification, and reporting workflows.',
|
|
400
|
+
fix: null });
|
|
401
|
+
}
|
|
402
|
+
return findings;
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
{ id: 'COMP-NIS2-002', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
407
|
+
title: 'No Supply Chain Security Assessment',
|
|
408
|
+
check({ files }) {
|
|
409
|
+
const findings = [];
|
|
410
|
+
const hasDeps = [...files.keys()].some(f => /package\.json|requirements\.txt|go\.mod|Gemfile|pom\.xml/i.test(f));
|
|
411
|
+
if (!hasDeps) return findings;
|
|
412
|
+
const hasSBOM = [...files.values()].some(c => /sbom|software.*bill.*of.*materials|supply.*chain|dependency.*audit|snyk|dependabot|renovate/i.test(c)) ||
|
|
413
|
+
[...files.keys()].some(f => /sbom|supply.chain/i.test(f));
|
|
414
|
+
if (!hasSBOM) {
|
|
415
|
+
findings.push({ ruleId: 'COMP-NIS2-002', category: 'compliance', severity: 'medium',
|
|
416
|
+
title: 'No supply chain security assessment or SBOM',
|
|
417
|
+
description: 'NIS2 requires supply chain security measures. Generate Software Bill of Materials (SBOM) and implement dependency vulnerability scanning.',
|
|
418
|
+
fix: null });
|
|
419
|
+
}
|
|
420
|
+
return findings;
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
{ id: 'COMP-NIS2-003', category: 'compliance', severity: 'high', confidence: 'likely',
|
|
425
|
+
title: 'Missing MFA for Critical Systems',
|
|
426
|
+
check({ files }) {
|
|
427
|
+
const findings = [];
|
|
428
|
+
const hasAdmin = [...files.values()].some(c => /admin|dashboard|management.*panel|backoffice|internal.*tool/i.test(c));
|
|
429
|
+
if (!hasAdmin) return findings;
|
|
430
|
+
const hasMFA = [...files.values()].some(c => /mfa|multi.factor|2fa|two.factor|totp|authenticator|otp|second.*factor/i.test(c));
|
|
431
|
+
if (!hasMFA) {
|
|
432
|
+
findings.push({ ruleId: 'COMP-NIS2-003', category: 'compliance', severity: 'high',
|
|
433
|
+
title: 'Admin/critical system access without multi-factor authentication',
|
|
434
|
+
description: 'NIS2 requires multi-factor authentication for critical system access. Implement MFA for all administrative and privileged operations.',
|
|
435
|
+
fix: null });
|
|
436
|
+
}
|
|
437
|
+
return findings;
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
{ id: 'COMP-NIS2-004', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
442
|
+
title: 'No Vulnerability Disclosure Policy',
|
|
443
|
+
check({ files }) {
|
|
444
|
+
const findings = [];
|
|
445
|
+
const hasSecurityTxt = [...files.keys()].some(f => /security\.txt|\.well-known\/security/i.test(f));
|
|
446
|
+
const hasPolicy = [...files.values()].some(c => /vulnerability.*disclosure|responsible.*disclosure|bug.*bounty|security.*report/i.test(c)) ||
|
|
447
|
+
[...files.keys()].some(f => /SECURITY\.md/i.test(f));
|
|
448
|
+
if (!hasSecurityTxt && !hasPolicy) {
|
|
449
|
+
const hasApp = [...files.keys()].some(f => /package\.json|requirements\.txt|go\.mod/i.test(f));
|
|
450
|
+
if (hasApp) {
|
|
451
|
+
findings.push({ ruleId: 'COMP-NIS2-004', category: 'compliance', severity: 'medium',
|
|
452
|
+
title: 'No vulnerability disclosure policy or security.txt',
|
|
453
|
+
description: 'NIS2 requires coordinated vulnerability disclosure. Add a SECURITY.md or .well-known/security.txt with reporting instructions.',
|
|
454
|
+
fix: null });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return findings;
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
{ id: 'COMP-NIS2-005', category: 'compliance', severity: 'medium', confidence: 'likely',
|
|
462
|
+
title: 'Missing Business Continuity Plan',
|
|
463
|
+
check({ files }) {
|
|
464
|
+
const findings = [];
|
|
465
|
+
const hasInfra = [...files.keys()].some(f => /\.tf$|docker|kubernetes|k8s|helm|cloudformation/i.test(f));
|
|
466
|
+
if (!hasInfra) return findings;
|
|
467
|
+
const hasBCP = [...files.values()].some(c => /business.*continuity|disaster.*recovery|failover|redundan|high.*availability|backup.*strategy|recovery.*point|recovery.*time/i.test(c)) ||
|
|
468
|
+
[...files.keys()].some(f => /disaster|recovery|continuity|bcp/i.test(f));
|
|
469
|
+
if (!hasBCP) {
|
|
470
|
+
findings.push({ ruleId: 'COMP-NIS2-005', category: 'compliance', severity: 'medium',
|
|
471
|
+
title: 'Infrastructure without business continuity planning',
|
|
472
|
+
description: 'NIS2 requires business continuity management. Document recovery time objectives (RTO), recovery point objectives (RPO), and failover procedures.',
|
|
473
|
+
fix: null });
|
|
474
|
+
}
|
|
475
|
+
return findings;
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
export default rules;
|