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,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust Security Rules (SEC-RS-001 through SEC-RS-040)
|
|
3
|
+
*
|
|
4
|
+
* Each rule scans Rust source files for security vulnerabilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const isRust = (f) => f.endsWith('.rs');
|
|
8
|
+
|
|
9
|
+
const SKIP_PATH = /[/\\](test|tests|__tests__|__mocks__|mocks|fixtures|__fixtures__|spec|__snapshots__|node_modules|vendor|dist|build|target)[/\\]/i;
|
|
10
|
+
const COMMENT_LINE = /^\s*(\/\/|\/\*|\*)/;
|
|
11
|
+
|
|
12
|
+
function scanLines(content, regex, file, rule) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
const lines = content.split('\n');
|
|
15
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16
|
+
const line = lines[i];
|
|
17
|
+
if (COMMENT_LINE.test(line)) continue;
|
|
18
|
+
if (regex.test(line)) {
|
|
19
|
+
findings.push({
|
|
20
|
+
ruleId: rule.id,
|
|
21
|
+
category: rule.category,
|
|
22
|
+
severity: rule.severity,
|
|
23
|
+
title: rule.title,
|
|
24
|
+
description: rule.description,
|
|
25
|
+
confidence: rule.confidence,
|
|
26
|
+
file,
|
|
27
|
+
line: i + 1,
|
|
28
|
+
fix: rule.fix || null,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return findings;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkRust(rule, files, ...patterns) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
for (const [path, content] of files) {
|
|
38
|
+
if (SKIP_PATH.test(path)) continue;
|
|
39
|
+
if (isRust(path)) {
|
|
40
|
+
for (const p of patterns) {
|
|
41
|
+
findings.push(...scanLines(content, p, path, rule));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return findings;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rules = [
|
|
49
|
+
// SEC-RS-001: Unsafe block usage
|
|
50
|
+
{
|
|
51
|
+
id: 'SEC-RS-001',
|
|
52
|
+
category: 'security',
|
|
53
|
+
severity: 'high',
|
|
54
|
+
confidence: 'likely',
|
|
55
|
+
title: 'Unsafe Block Without Safety Comment',
|
|
56
|
+
description: 'Unsafe blocks bypass Rust memory safety guarantees and require careful justification.',
|
|
57
|
+
fix: { suggestion: 'Document safety invariants with a // SAFETY: comment. Minimize unsafe scope.' },
|
|
58
|
+
check({ files }) {
|
|
59
|
+
return checkRust(this, files,
|
|
60
|
+
/unsafe\s*\{/,
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// SEC-RS-002: Unsafe function declaration
|
|
66
|
+
{
|
|
67
|
+
id: 'SEC-RS-002',
|
|
68
|
+
category: 'security',
|
|
69
|
+
severity: 'high',
|
|
70
|
+
confidence: 'likely',
|
|
71
|
+
title: 'Unsafe Function Declaration',
|
|
72
|
+
description: 'Unsafe functions require callers to uphold safety invariants that the compiler cannot verify.',
|
|
73
|
+
fix: { suggestion: 'Document safety requirements with # Safety in doc comments. Consider a safe wrapper.' },
|
|
74
|
+
check({ files }) {
|
|
75
|
+
return checkRust(this, files,
|
|
76
|
+
/unsafe\s+fn\s+/,
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// SEC-RS-003: unwrap() on user input
|
|
82
|
+
{
|
|
83
|
+
id: 'SEC-RS-003',
|
|
84
|
+
category: 'security',
|
|
85
|
+
severity: 'high',
|
|
86
|
+
confidence: 'likely',
|
|
87
|
+
title: 'unwrap() Can Panic in Production',
|
|
88
|
+
description: 'Calling unwrap() on Result/Option from user input can cause panics and denial of service.',
|
|
89
|
+
fix: { suggestion: 'Use match, if let, unwrap_or, unwrap_or_else, or the ? operator instead of unwrap().' },
|
|
90
|
+
check({ files }) {
|
|
91
|
+
return checkRust(this, files,
|
|
92
|
+
/\.unwrap\s*\(\s*\)/,
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// SEC-RS-004: expect() on user input
|
|
98
|
+
{
|
|
99
|
+
id: 'SEC-RS-004',
|
|
100
|
+
category: 'security',
|
|
101
|
+
severity: 'medium',
|
|
102
|
+
confidence: 'suggestion',
|
|
103
|
+
title: 'expect() Can Panic in Production',
|
|
104
|
+
description: 'Calling expect() on Result/Option from external input can cause panics.',
|
|
105
|
+
fix: { suggestion: 'Use proper error handling with match or the ? operator instead of expect().' },
|
|
106
|
+
check({ files }) {
|
|
107
|
+
return checkRust(this, files,
|
|
108
|
+
/\.expect\s*\(\s*["']/,
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// SEC-RS-005: SQL injection with format!
|
|
114
|
+
{
|
|
115
|
+
id: 'SEC-RS-005',
|
|
116
|
+
category: 'security',
|
|
117
|
+
severity: 'critical',
|
|
118
|
+
confidence: 'likely',
|
|
119
|
+
title: 'SQL Injection via format! in Query',
|
|
120
|
+
description: 'Using format! to build SQL queries allows injection attacks.',
|
|
121
|
+
fix: { suggestion: 'Use parameterized queries with $1 placeholders or the query builder API.' },
|
|
122
|
+
check({ files }) {
|
|
123
|
+
return checkRust(this, files,
|
|
124
|
+
/(?:query|execute)\s*\(\s*&?format!\s*\(/,
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// SEC-RS-006: SQL injection with string concatenation
|
|
130
|
+
{
|
|
131
|
+
id: 'SEC-RS-006',
|
|
132
|
+
category: 'security',
|
|
133
|
+
severity: 'critical',
|
|
134
|
+
confidence: 'likely',
|
|
135
|
+
title: 'SQL Injection via String Concatenation',
|
|
136
|
+
description: 'Building SQL queries with string concatenation allows injection.',
|
|
137
|
+
fix: { suggestion: 'Use parameterized queries provided by your database driver.' },
|
|
138
|
+
check({ files }) {
|
|
139
|
+
return checkRust(this, files,
|
|
140
|
+
/(?:query|execute)\s*\(\s*&?\s*\(?\s*(?:["']SELECT|["']INSERT|["']UPDATE|["']DELETE).*\+/i,
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// SEC-RS-007: Command injection via Command::new
|
|
146
|
+
{
|
|
147
|
+
id: 'SEC-RS-007',
|
|
148
|
+
category: 'security',
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
confidence: 'likely',
|
|
151
|
+
title: 'Command Injection via Command::new',
|
|
152
|
+
description: 'Passing user-controlled input to Command::new or .arg() can lead to command injection.',
|
|
153
|
+
fix: { suggestion: 'Validate and sanitize user input. Avoid passing user input directly to system commands.' },
|
|
154
|
+
check({ files }) {
|
|
155
|
+
return checkRust(this, files,
|
|
156
|
+
/Command::new\s*\(\s*&?\s*(?:input|user|req|param|query|body|arg)/,
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// SEC-RS-008: Command injection via shell
|
|
162
|
+
{
|
|
163
|
+
id: 'SEC-RS-008',
|
|
164
|
+
category: 'security',
|
|
165
|
+
severity: 'critical',
|
|
166
|
+
confidence: 'likely',
|
|
167
|
+
title: 'Command Injection via Shell Execution',
|
|
168
|
+
description: 'Using Command::new("sh") or Command::new("bash") with -c flag and user input enables injection.',
|
|
169
|
+
fix: { suggestion: 'Use Command::new with the program directly and pass arguments via .arg().' },
|
|
170
|
+
check({ files }) {
|
|
171
|
+
return checkRust(this, files,
|
|
172
|
+
/Command::new\s*\(\s*["'](?:sh|bash|\/bin\/(?:ba)?sh)["']\s*\)/,
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// SEC-RS-009: Path traversal with unchecked paths
|
|
178
|
+
{
|
|
179
|
+
id: 'SEC-RS-009',
|
|
180
|
+
category: 'security',
|
|
181
|
+
severity: 'high',
|
|
182
|
+
confidence: 'likely',
|
|
183
|
+
title: 'Path Traversal: Unchecked User Path',
|
|
184
|
+
description: 'Using user-supplied paths without validation may allow directory traversal.',
|
|
185
|
+
fix: { suggestion: 'Canonicalize paths and verify they start with the expected base directory.' },
|
|
186
|
+
check({ files }) {
|
|
187
|
+
return checkRust(this, files,
|
|
188
|
+
/(?:Path::new|PathBuf::from)\s*\(\s*&?\s*(?:input|user|req|param|query|body)/,
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// SEC-RS-010: Path traversal via std::fs with user input
|
|
194
|
+
{
|
|
195
|
+
id: 'SEC-RS-010',
|
|
196
|
+
category: 'security',
|
|
197
|
+
severity: 'high',
|
|
198
|
+
confidence: 'likely',
|
|
199
|
+
title: 'Path Traversal via File Operations',
|
|
200
|
+
description: 'File operations with user-controlled paths can read or write arbitrary files.',
|
|
201
|
+
fix: { suggestion: 'Validate paths with canonicalize() and check the prefix matches expected directory.' },
|
|
202
|
+
check({ files }) {
|
|
203
|
+
return checkRust(this, files,
|
|
204
|
+
/(?:fs::read|fs::write|fs::remove|File::open|File::create)\s*\(\s*&?\s*(?:input|user|req|param|query|body)/,
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// SEC-RS-011: Hardcoded secret
|
|
210
|
+
{
|
|
211
|
+
id: 'SEC-RS-011',
|
|
212
|
+
category: 'security',
|
|
213
|
+
severity: 'critical',
|
|
214
|
+
confidence: 'likely',
|
|
215
|
+
title: 'Hardcoded Secret',
|
|
216
|
+
description: 'Secrets hardcoded in source code can be extracted from binaries.',
|
|
217
|
+
fix: { suggestion: 'Use environment variables or a secrets manager for credentials.' },
|
|
218
|
+
check({ files }) {
|
|
219
|
+
return checkRust(this, files,
|
|
220
|
+
/(?:password|secret|api_key|token|credential)\s*(?:=|:)\s*["'][^"']{4,}["']/i,
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// SEC-RS-012: Hardcoded connection string
|
|
226
|
+
{
|
|
227
|
+
id: 'SEC-RS-012',
|
|
228
|
+
category: 'security',
|
|
229
|
+
severity: 'high',
|
|
230
|
+
confidence: 'likely',
|
|
231
|
+
title: 'Hardcoded Database Connection String',
|
|
232
|
+
description: 'Database connection strings with credentials hardcoded in source code.',
|
|
233
|
+
fix: { suggestion: 'Use environment variables for database connection strings.' },
|
|
234
|
+
check({ files }) {
|
|
235
|
+
return checkRust(this, files,
|
|
236
|
+
/["'](?:postgres|mysql|mongodb|redis):\/\/\w+:\w+@/,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// SEC-RS-013: Insecure random - rand::thread_rng for crypto
|
|
242
|
+
{
|
|
243
|
+
id: 'SEC-RS-013',
|
|
244
|
+
category: 'security',
|
|
245
|
+
severity: 'high',
|
|
246
|
+
confidence: 'likely',
|
|
247
|
+
title: 'Insecure Random Number Generator',
|
|
248
|
+
description: 'Using rand::thread_rng or rand::random for security-sensitive operations is insecure.',
|
|
249
|
+
fix: { suggestion: 'Use rand::rngs::OsRng or ring::rand::SystemRandom for cryptographic operations.' },
|
|
250
|
+
check({ files }) {
|
|
251
|
+
return checkRust(this, files,
|
|
252
|
+
/rand::(?:thread_rng|random)\s*\(\s*\)/,
|
|
253
|
+
);
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// SEC-RS-014: Insecure random - SmallRng
|
|
258
|
+
{
|
|
259
|
+
id: 'SEC-RS-014',
|
|
260
|
+
category: 'security',
|
|
261
|
+
severity: 'high',
|
|
262
|
+
confidence: 'likely',
|
|
263
|
+
title: 'SmallRng Not Suitable for Security',
|
|
264
|
+
description: 'SmallRng is designed for performance, not cryptographic security.',
|
|
265
|
+
fix: { suggestion: 'Use OsRng for security-sensitive random number generation.' },
|
|
266
|
+
check({ files }) {
|
|
267
|
+
return checkRust(this, files,
|
|
268
|
+
/SmallRng::from_entropy|SmallRng::seed_from/,
|
|
269
|
+
);
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// SEC-RS-015: Missing TLS certificate validation
|
|
274
|
+
{
|
|
275
|
+
id: 'SEC-RS-015',
|
|
276
|
+
category: 'security',
|
|
277
|
+
severity: 'critical',
|
|
278
|
+
confidence: 'definite',
|
|
279
|
+
title: 'TLS Certificate Validation Disabled',
|
|
280
|
+
description: 'Disabling TLS certificate validation allows man-in-the-middle attacks.',
|
|
281
|
+
fix: { suggestion: 'Enable certificate validation. Use proper CA certificates.' },
|
|
282
|
+
check({ files }) {
|
|
283
|
+
return checkRust(this, files,
|
|
284
|
+
/danger_accept_invalid_certs\s*\(\s*true\s*\)/,
|
|
285
|
+
);
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
// SEC-RS-016: Missing hostname validation
|
|
290
|
+
{
|
|
291
|
+
id: 'SEC-RS-016',
|
|
292
|
+
category: 'security',
|
|
293
|
+
severity: 'critical',
|
|
294
|
+
confidence: 'definite',
|
|
295
|
+
title: 'TLS Hostname Validation Disabled',
|
|
296
|
+
description: 'Disabling hostname validation in TLS allows certificate substitution attacks.',
|
|
297
|
+
fix: { suggestion: 'Enable hostname verification for TLS connections.' },
|
|
298
|
+
check({ files }) {
|
|
299
|
+
return checkRust(this, files,
|
|
300
|
+
/danger_accept_invalid_hostnames\s*\(\s*true\s*\)/,
|
|
301
|
+
);
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
// SEC-RS-017: Regex DoS
|
|
306
|
+
{
|
|
307
|
+
id: 'SEC-RS-017',
|
|
308
|
+
category: 'security',
|
|
309
|
+
severity: 'medium',
|
|
310
|
+
confidence: 'suggestion',
|
|
311
|
+
title: 'Regex DoS: Potentially Catastrophic Backtracking',
|
|
312
|
+
description: 'Complex regex patterns with nested quantifiers can cause catastrophic backtracking on crafted input.',
|
|
313
|
+
fix: { suggestion: 'Use the regex crate (RE2-based, linear time) instead of regex with backtracking. Avoid nested quantifiers.' },
|
|
314
|
+
check({ files }) {
|
|
315
|
+
return checkRust(this, files,
|
|
316
|
+
/Regex::new\s*\(\s*["'].*(?:\.\*){2,}/,
|
|
317
|
+
/Regex::new\s*\(\s*["'].*(?:\+\+|\*\*|\?\?)/,
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// SEC-RS-018: transmute usage
|
|
323
|
+
{
|
|
324
|
+
id: 'SEC-RS-018',
|
|
325
|
+
category: 'security',
|
|
326
|
+
severity: 'high',
|
|
327
|
+
confidence: 'likely',
|
|
328
|
+
title: 'Unsafe transmute Usage',
|
|
329
|
+
description: 'std::mem::transmute can cause undefined behavior if types are not layout-compatible.',
|
|
330
|
+
fix: { suggestion: 'Use safe alternatives like From/Into traits, TryFrom, or bytemuck.' },
|
|
331
|
+
check({ files }) {
|
|
332
|
+
return checkRust(this, files,
|
|
333
|
+
/(?:mem::)?transmute\s*[:<(]/,
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
// SEC-RS-019: transmute_copy usage
|
|
339
|
+
{
|
|
340
|
+
id: 'SEC-RS-019',
|
|
341
|
+
category: 'security',
|
|
342
|
+
severity: 'high',
|
|
343
|
+
confidence: 'likely',
|
|
344
|
+
title: 'Unsafe transmute_copy Usage',
|
|
345
|
+
description: 'transmute_copy can read uninitialized memory and cause undefined behavior.',
|
|
346
|
+
fix: { suggestion: 'Use safe conversion methods instead of transmute_copy.' },
|
|
347
|
+
check({ files }) {
|
|
348
|
+
return checkRust(this, files,
|
|
349
|
+
/transmute_copy\s*[:<(]/,
|
|
350
|
+
);
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
// SEC-RS-020: Raw pointer dereference
|
|
355
|
+
{
|
|
356
|
+
id: 'SEC-RS-020',
|
|
357
|
+
category: 'security',
|
|
358
|
+
severity: 'high',
|
|
359
|
+
confidence: 'likely',
|
|
360
|
+
title: 'Raw Pointer Dereference',
|
|
361
|
+
description: 'Dereferencing raw pointers (*const T, *mut T) is unsafe and can cause undefined behavior.',
|
|
362
|
+
fix: { suggestion: 'Use references (&T, &mut T) instead of raw pointers when possible.' },
|
|
363
|
+
check({ files }) {
|
|
364
|
+
return checkRust(this, files,
|
|
365
|
+
/\*(?:const|mut)\s+\w+/,
|
|
366
|
+
);
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
// SEC-RS-021: from_raw_parts without bounds checking
|
|
371
|
+
{
|
|
372
|
+
id: 'SEC-RS-021',
|
|
373
|
+
category: 'security',
|
|
374
|
+
severity: 'critical',
|
|
375
|
+
confidence: 'likely',
|
|
376
|
+
title: 'from_raw_parts Without Bounds Checking',
|
|
377
|
+
description: 'slice::from_raw_parts creates a slice from a raw pointer without verifying bounds, risking buffer overflows.',
|
|
378
|
+
fix: { suggestion: 'Verify that the pointer is valid and the length does not exceed the allocated memory.' },
|
|
379
|
+
check({ files }) {
|
|
380
|
+
return checkRust(this, files,
|
|
381
|
+
/from_raw_parts(?:_mut)?\s*\(/,
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// SEC-RS-022: Missing CSRF in Actix
|
|
387
|
+
{
|
|
388
|
+
id: 'SEC-RS-022',
|
|
389
|
+
category: 'security',
|
|
390
|
+
severity: 'high',
|
|
391
|
+
confidence: 'suggestion',
|
|
392
|
+
title: 'Missing CSRF Protection in Actix Web',
|
|
393
|
+
description: 'POST/PUT/DELETE routes without CSRF protection are vulnerable to cross-site request forgery.',
|
|
394
|
+
fix: { suggestion: 'Add CSRF middleware or token validation for state-changing routes.' },
|
|
395
|
+
check({ files }) {
|
|
396
|
+
return checkRust(this, files,
|
|
397
|
+
/\.route\s*\(\s*["'][^"']+["']\s*,\s*web::(?:post|put|delete|patch)\s*\(\s*\)/,
|
|
398
|
+
);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
// SEC-RS-023: Missing CSRF in Axum
|
|
403
|
+
{
|
|
404
|
+
id: 'SEC-RS-023',
|
|
405
|
+
category: 'security',
|
|
406
|
+
severity: 'high',
|
|
407
|
+
confidence: 'suggestion',
|
|
408
|
+
title: 'Missing CSRF Protection in Axum',
|
|
409
|
+
description: 'POST/PUT/DELETE routes in Axum without CSRF middleware are vulnerable.',
|
|
410
|
+
fix: { suggestion: 'Add CSRF protection middleware for state-changing endpoints.' },
|
|
411
|
+
check({ files }) {
|
|
412
|
+
return checkRust(this, files,
|
|
413
|
+
/\.(?:post|put|delete|patch)\s*\(\s*["'][^"']+["']/,
|
|
414
|
+
);
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
// SEC-RS-024: Insecure deserialization with serde
|
|
419
|
+
{
|
|
420
|
+
id: 'SEC-RS-024',
|
|
421
|
+
category: 'security',
|
|
422
|
+
severity: 'high',
|
|
423
|
+
confidence: 'suggestion',
|
|
424
|
+
title: 'Potentially Insecure Deserialization',
|
|
425
|
+
description: 'Deserializing untrusted data without validation can lead to logic bugs or resource exhaustion.',
|
|
426
|
+
fix: { suggestion: 'Validate deserialized data. Use #[serde(deny_unknown_fields)] and size limits.' },
|
|
427
|
+
check({ files }) {
|
|
428
|
+
return checkRust(this, files,
|
|
429
|
+
/serde_json::from_str\s*\(\s*&?\s*(?:input|body|request|payload|data)/,
|
|
430
|
+
);
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
// SEC-RS-025: Insecure deserialization with bincode
|
|
435
|
+
{
|
|
436
|
+
id: 'SEC-RS-025',
|
|
437
|
+
category: 'security',
|
|
438
|
+
severity: 'high',
|
|
439
|
+
confidence: 'likely',
|
|
440
|
+
title: 'Insecure Deserialization with bincode',
|
|
441
|
+
description: 'Deserializing untrusted binary data with bincode can cause resource exhaustion or crashes.',
|
|
442
|
+
fix: { suggestion: 'Use bincode::Options with size limits. Validate input before deserialization.' },
|
|
443
|
+
check({ files }) {
|
|
444
|
+
return checkRust(this, files,
|
|
445
|
+
/bincode::deserialize\s*\(/,
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
// SEC-RS-026: Missing rate limiting
|
|
451
|
+
{
|
|
452
|
+
id: 'SEC-RS-026',
|
|
453
|
+
category: 'security',
|
|
454
|
+
severity: 'medium',
|
|
455
|
+
confidence: 'suggestion',
|
|
456
|
+
title: 'Missing Rate Limiting on HTTP Endpoint',
|
|
457
|
+
description: 'HTTP endpoints without rate limiting are vulnerable to brute force and DoS attacks.',
|
|
458
|
+
fix: { suggestion: 'Add rate limiting middleware using actix-limitation, tower::limit, or governor.' },
|
|
459
|
+
check({ files }) {
|
|
460
|
+
return checkRust(this, files,
|
|
461
|
+
/async\s+fn\s+\w+\s*\([^)]*(?:HttpRequest|Request|Json|Path|Query)/,
|
|
462
|
+
);
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
// SEC-RS-027: Panic in async context
|
|
467
|
+
{
|
|
468
|
+
id: 'SEC-RS-027',
|
|
469
|
+
category: 'security',
|
|
470
|
+
severity: 'high',
|
|
471
|
+
confidence: 'likely',
|
|
472
|
+
title: 'Panic in Async Context',
|
|
473
|
+
description: 'Panicking in async code can abort the entire runtime, causing denial of service.',
|
|
474
|
+
fix: { suggestion: 'Use Result types and proper error handling instead of panic! in async functions.' },
|
|
475
|
+
check({ files }) {
|
|
476
|
+
return checkRust(this, files,
|
|
477
|
+
/async\s+fn\s+[\s\S]*panic!\s*\(/,
|
|
478
|
+
/async\s+fn[\s\S]*todo!\s*\(/,
|
|
479
|
+
);
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
// SEC-RS-028: Missing input validation in Actix handler
|
|
484
|
+
{
|
|
485
|
+
id: 'SEC-RS-028',
|
|
486
|
+
category: 'security',
|
|
487
|
+
severity: 'medium',
|
|
488
|
+
confidence: 'suggestion',
|
|
489
|
+
title: 'Missing Input Validation in Actix Handler',
|
|
490
|
+
description: 'Actix web handlers accepting user input without validation may process malformed data.',
|
|
491
|
+
fix: { suggestion: 'Use actix-web-validator or implement the Validate trait on input structs.' },
|
|
492
|
+
check({ files }) {
|
|
493
|
+
return checkRust(this, files,
|
|
494
|
+
/web::(?:Json|Path|Query)<[^>]+>\s*\)/,
|
|
495
|
+
);
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
// SEC-RS-029: Missing input validation in Axum handler
|
|
500
|
+
{
|
|
501
|
+
id: 'SEC-RS-029',
|
|
502
|
+
category: 'security',
|
|
503
|
+
severity: 'medium',
|
|
504
|
+
confidence: 'suggestion',
|
|
505
|
+
title: 'Missing Input Validation in Axum Handler',
|
|
506
|
+
description: 'Axum handlers accepting extractors without validation may process malformed data.',
|
|
507
|
+
fix: { suggestion: 'Use axum-extra validated extractors or implement custom validation.' },
|
|
508
|
+
check({ files }) {
|
|
509
|
+
return checkRust(this, files,
|
|
510
|
+
/(?:Json|Path|Query)<[^>]+>\s*\)\s*->/,
|
|
511
|
+
);
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
// SEC-RS-030: CORS wildcard
|
|
516
|
+
{
|
|
517
|
+
id: 'SEC-RS-030',
|
|
518
|
+
category: 'security',
|
|
519
|
+
severity: 'high',
|
|
520
|
+
confidence: 'definite',
|
|
521
|
+
title: 'CORS Wildcard Origin',
|
|
522
|
+
description: 'Allowing all origins for CORS enables any website to make cross-origin requests.',
|
|
523
|
+
fix: { suggestion: 'Specify allowed origins explicitly instead of using permissive().' },
|
|
524
|
+
check({ files }) {
|
|
525
|
+
return checkRust(this, files,
|
|
526
|
+
/Cors::(?:permissive|default)\s*\(\s*\)/,
|
|
527
|
+
/\.allow_any_origin\s*\(\s*\)/,
|
|
528
|
+
);
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
// SEC-RS-031: SSRF via reqwest
|
|
533
|
+
{
|
|
534
|
+
id: 'SEC-RS-031',
|
|
535
|
+
category: 'security',
|
|
536
|
+
severity: 'high',
|
|
537
|
+
confidence: 'likely',
|
|
538
|
+
title: 'SSRF via User-Controlled URL in reqwest',
|
|
539
|
+
description: 'Passing user-controlled URLs to reqwest enables Server-Side Request Forgery.',
|
|
540
|
+
fix: { suggestion: 'Validate and whitelist allowed URLs/hosts before making HTTP requests.' },
|
|
541
|
+
check({ files }) {
|
|
542
|
+
return checkRust(this, files,
|
|
543
|
+
/(?:reqwest|Client)::(?:get|post|put|delete)\s*\(\s*&?\s*(?:input|user|url|param|query|body)/,
|
|
544
|
+
);
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
// SEC-RS-032: Hardcoded private key
|
|
549
|
+
{
|
|
550
|
+
id: 'SEC-RS-032',
|
|
551
|
+
category: 'security',
|
|
552
|
+
severity: 'critical',
|
|
553
|
+
confidence: 'definite',
|
|
554
|
+
title: 'Hardcoded Private Key',
|
|
555
|
+
description: 'Private keys embedded in source code can be extracted and used to impersonate the service.',
|
|
556
|
+
fix: { suggestion: 'Load private keys from files or environment variables at runtime.' },
|
|
557
|
+
check({ files }) {
|
|
558
|
+
return checkRust(this, files,
|
|
559
|
+
/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
|
|
560
|
+
);
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
// SEC-RS-033: Log sensitive data
|
|
565
|
+
{
|
|
566
|
+
id: 'SEC-RS-033',
|
|
567
|
+
category: 'security',
|
|
568
|
+
severity: 'medium',
|
|
569
|
+
confidence: 'likely',
|
|
570
|
+
title: 'Sensitive Data in Log Output',
|
|
571
|
+
description: 'Logging passwords, tokens, or keys can expose sensitive data.',
|
|
572
|
+
fix: { suggestion: 'Redact sensitive fields before logging.' },
|
|
573
|
+
check({ files }) {
|
|
574
|
+
return checkRust(this, files,
|
|
575
|
+
/(?:log::(?:info|debug|warn|error|trace)|(?:info|debug|warn|error|trace)!)\s*\(.*(?:password|token|secret|key|credential)/i,
|
|
576
|
+
);
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
// SEC-RS-034: Unvalidated redirect
|
|
581
|
+
{
|
|
582
|
+
id: 'SEC-RS-034',
|
|
583
|
+
category: 'security',
|
|
584
|
+
severity: 'high',
|
|
585
|
+
confidence: 'likely',
|
|
586
|
+
title: 'Open Redirect',
|
|
587
|
+
description: 'Redirecting to user-supplied URLs without validation enables phishing.',
|
|
588
|
+
fix: { suggestion: 'Validate redirect URLs against an allowlist of trusted domains.' },
|
|
589
|
+
check({ files }) {
|
|
590
|
+
return checkRust(this, files,
|
|
591
|
+
/(?:Redirect|redirect)\s*::\s*(?:to|temporary|permanent)\s*\(\s*&?\s*(?:input|user|url|param|query)/,
|
|
592
|
+
);
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
|
|
596
|
+
// SEC-RS-035: Memory leak via Box::into_raw
|
|
597
|
+
{
|
|
598
|
+
id: 'SEC-RS-035',
|
|
599
|
+
category: 'security',
|
|
600
|
+
severity: 'medium',
|
|
601
|
+
confidence: 'likely',
|
|
602
|
+
title: 'Potential Memory Leak via Box::into_raw',
|
|
603
|
+
description: 'Box::into_raw transfers ownership to a raw pointer. Failing to reconstruct the Box leaks memory.',
|
|
604
|
+
fix: { suggestion: 'Ensure Box::from_raw is called to reclaim the memory.' },
|
|
605
|
+
check({ files }) {
|
|
606
|
+
return checkRust(this, files,
|
|
607
|
+
/Box::into_raw\s*\(/,
|
|
608
|
+
);
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
// SEC-RS-036: Weak crypto - MD5
|
|
613
|
+
{
|
|
614
|
+
id: 'SEC-RS-036',
|
|
615
|
+
category: 'security',
|
|
616
|
+
severity: 'high',
|
|
617
|
+
confidence: 'definite',
|
|
618
|
+
title: 'Weak Cryptographic Hash: MD5',
|
|
619
|
+
description: 'MD5 is cryptographically broken and must not be used for security purposes.',
|
|
620
|
+
fix: { suggestion: 'Use SHA-256 or SHA-3 via the sha2 or sha3 crate.' },
|
|
621
|
+
check({ files }) {
|
|
622
|
+
return checkRust(this, files,
|
|
623
|
+
/(?:Md5|md5)::(?:new|digest|compute)\s*\(/,
|
|
624
|
+
);
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
// SEC-RS-037: Weak crypto - SHA1
|
|
629
|
+
{
|
|
630
|
+
id: 'SEC-RS-037',
|
|
631
|
+
category: 'security',
|
|
632
|
+
severity: 'high',
|
|
633
|
+
confidence: 'definite',
|
|
634
|
+
title: 'Weak Cryptographic Hash: SHA1',
|
|
635
|
+
description: 'SHA1 has known collision attacks and should not be used for security.',
|
|
636
|
+
fix: { suggestion: 'Use SHA-256 or SHA-3 instead of SHA1.' },
|
|
637
|
+
check({ files }) {
|
|
638
|
+
return checkRust(this, files,
|
|
639
|
+
/(?:Sha1|sha1)::(?:new|digest|compute)\s*\(/,
|
|
640
|
+
);
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
// SEC-RS-038: Cookie without secure flag
|
|
645
|
+
{
|
|
646
|
+
id: 'SEC-RS-038',
|
|
647
|
+
category: 'security',
|
|
648
|
+
severity: 'high',
|
|
649
|
+
confidence: 'likely',
|
|
650
|
+
title: 'Cookie Without Secure Flag',
|
|
651
|
+
description: 'Cookies without the Secure flag may be sent over unencrypted HTTP.',
|
|
652
|
+
fix: { suggestion: 'Set .secure(true) on cookies.' },
|
|
653
|
+
check({ files }) {
|
|
654
|
+
return checkRust(this, files,
|
|
655
|
+
/Cookie::build\s*\([^)]*\)(?:(?!\.secure\s*\(\s*true)[\s\S])*\.finish/,
|
|
656
|
+
);
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
// SEC-RS-039: Integer overflow in release mode
|
|
661
|
+
{
|
|
662
|
+
id: 'SEC-RS-039',
|
|
663
|
+
category: 'security',
|
|
664
|
+
severity: 'medium',
|
|
665
|
+
confidence: 'suggestion',
|
|
666
|
+
title: 'Potential Integer Overflow',
|
|
667
|
+
description: 'Integer overflow wraps in release mode. Use checked arithmetic for security-critical calculations.',
|
|
668
|
+
fix: { suggestion: 'Use checked_add, checked_mul, saturating_add, or wrapping_add for explicit overflow handling.' },
|
|
669
|
+
check({ files }) {
|
|
670
|
+
return checkRust(this, files,
|
|
671
|
+
/as\s+(?:u8|u16|i8|i16|u32|i32)\s*[;)]/,
|
|
672
|
+
);
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
// SEC-RS-040: Unprotected shared state
|
|
677
|
+
{
|
|
678
|
+
id: 'SEC-RS-040',
|
|
679
|
+
category: 'security',
|
|
680
|
+
severity: 'high',
|
|
681
|
+
confidence: 'suggestion',
|
|
682
|
+
title: 'Shared Mutable State Without Synchronization',
|
|
683
|
+
description: 'Using Rc<RefCell<>> in multi-threaded context causes undefined behavior. Use Arc<Mutex<>>.',
|
|
684
|
+
fix: { suggestion: 'Use Arc<Mutex<T>> or Arc<RwLock<T>> for shared state in concurrent code.' },
|
|
685
|
+
check({ files }) {
|
|
686
|
+
return checkRust(this, files,
|
|
687
|
+
/Rc\s*<\s*RefCell\s*</,
|
|
688
|
+
);
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
];
|
|
692
|
+
|
|
693
|
+
export default rules;
|