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,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Script Security Rules (SEC-SHELL-001 through SEC-SHELL-030)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const isShell = (f) => /\.(?:sh|bash|zsh)$/.test(f);
|
|
6
|
+
|
|
7
|
+
const SKIP_PATH = /[/\\](test|tests|__tests__|__mocks__|mocks|fixtures|__fixtures__|spec|__snapshots__|node_modules|vendor|dist|build)[/\\]/i;
|
|
8
|
+
const COMMENT_LINE = /^\s*#/;
|
|
9
|
+
|
|
10
|
+
function scanLines(content, regex, file, rule) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const lines = content.split('\n');
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
if (COMMENT_LINE.test(line)) continue;
|
|
16
|
+
if (regex.test(line)) {
|
|
17
|
+
findings.push({
|
|
18
|
+
ruleId: rule.id,
|
|
19
|
+
category: rule.category,
|
|
20
|
+
severity: rule.severity,
|
|
21
|
+
title: rule.title,
|
|
22
|
+
description: rule.description,
|
|
23
|
+
confidence: rule.confidence,
|
|
24
|
+
file,
|
|
25
|
+
line: i + 1,
|
|
26
|
+
fix: rule.fix || null,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return findings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rules = [
|
|
34
|
+
// SEC-SHELL-001: Unquoted variables
|
|
35
|
+
{
|
|
36
|
+
id: 'SEC-SHELL-001',
|
|
37
|
+
category: 'security',
|
|
38
|
+
severity: 'medium',
|
|
39
|
+
confidence: 'likely',
|
|
40
|
+
title: 'Unquoted Variable Expansion',
|
|
41
|
+
description: 'Unquoted variables are subject to word splitting and glob expansion, which can lead to unexpected behavior or injection.',
|
|
42
|
+
fix: { suggestion: 'Always quote variable expansions: use "$VAR" instead of $VAR.' },
|
|
43
|
+
check({ files }) {
|
|
44
|
+
const findings = [];
|
|
45
|
+
const pattern = /(?:^|[\s;|&])(?:cp|mv|rm|cat|echo|cd|ls|mkdir|chmod|chown|touch)\s+(?:[^"']*\s)?\$\w+(?!\w)/;
|
|
46
|
+
for (const [path, content] of files) {
|
|
47
|
+
if (SKIP_PATH.test(path)) continue;
|
|
48
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
49
|
+
}
|
|
50
|
+
return findings;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// SEC-SHELL-002: eval with user input
|
|
55
|
+
{
|
|
56
|
+
id: 'SEC-SHELL-002',
|
|
57
|
+
category: 'security',
|
|
58
|
+
severity: 'critical',
|
|
59
|
+
confidence: 'likely',
|
|
60
|
+
title: 'eval with Variable Input',
|
|
61
|
+
description: 'eval executes arbitrary code and is dangerous when used with user-controlled input.',
|
|
62
|
+
fix: { suggestion: 'Avoid eval. Use arrays, case statements, or parameter expansion instead.' },
|
|
63
|
+
check({ files }) {
|
|
64
|
+
const findings = [];
|
|
65
|
+
const pattern = /\beval\s+.*\$/;
|
|
66
|
+
for (const [path, content] of files) {
|
|
67
|
+
if (SKIP_PATH.test(path)) continue;
|
|
68
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
69
|
+
}
|
|
70
|
+
return findings;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// SEC-SHELL-003: curl | bash
|
|
75
|
+
{
|
|
76
|
+
id: 'SEC-SHELL-003',
|
|
77
|
+
category: 'security',
|
|
78
|
+
severity: 'critical',
|
|
79
|
+
confidence: 'likely',
|
|
80
|
+
title: 'Pipe to Shell (curl | bash)',
|
|
81
|
+
description: 'Piping downloaded content directly to a shell executes untrusted code without inspection.',
|
|
82
|
+
fix: { suggestion: 'Download the script first, inspect it, verify checksums, then execute.' },
|
|
83
|
+
check({ files }) {
|
|
84
|
+
const findings = [];
|
|
85
|
+
const pattern = /(?:curl|wget)\s+.*\|\s*(?:bash|sh|zsh|sudo\s+(?:bash|sh))/;
|
|
86
|
+
for (const [path, content] of files) {
|
|
87
|
+
if (SKIP_PATH.test(path)) continue;
|
|
88
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// SEC-SHELL-004: chmod 777
|
|
95
|
+
{
|
|
96
|
+
id: 'SEC-SHELL-004',
|
|
97
|
+
category: 'security',
|
|
98
|
+
severity: 'high',
|
|
99
|
+
confidence: 'likely',
|
|
100
|
+
title: 'World-Writable Permissions (chmod 777)',
|
|
101
|
+
description: 'chmod 777 gives read/write/execute to all users, allowing any user to modify the file.',
|
|
102
|
+
fix: { suggestion: 'Use the least permissive mode needed. For executables, use 755; for config files, 644 or 600.' },
|
|
103
|
+
check({ files }) {
|
|
104
|
+
const findings = [];
|
|
105
|
+
const pattern = /chmod\s+(?:777|a\+rwx|\+rwx)/;
|
|
106
|
+
for (const [path, content] of files) {
|
|
107
|
+
if (SKIP_PATH.test(path)) continue;
|
|
108
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
109
|
+
}
|
|
110
|
+
return findings;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// SEC-SHELL-005: Hardcoded passwords
|
|
115
|
+
{
|
|
116
|
+
id: 'SEC-SHELL-005',
|
|
117
|
+
category: 'security',
|
|
118
|
+
severity: 'critical',
|
|
119
|
+
confidence: 'likely',
|
|
120
|
+
title: 'Hardcoded Password in Script',
|
|
121
|
+
description: 'Passwords hardcoded in shell scripts can be read by anyone with file access.',
|
|
122
|
+
fix: { suggestion: 'Use environment variables, secret managers (Vault, AWS SSM), or prompt for credentials at runtime.' },
|
|
123
|
+
check({ files }) {
|
|
124
|
+
const findings = [];
|
|
125
|
+
const pattern = /(?:PASSWORD|PASSWD|SECRET|API_KEY|TOKEN)\s*=\s*['"][^$'"]+['"]/;
|
|
126
|
+
for (const [path, content] of files) {
|
|
127
|
+
if (SKIP_PATH.test(path)) continue;
|
|
128
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
129
|
+
}
|
|
130
|
+
return findings;
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// SEC-SHELL-006: Missing set -euo pipefail
|
|
135
|
+
{
|
|
136
|
+
id: 'SEC-SHELL-006',
|
|
137
|
+
category: 'security',
|
|
138
|
+
severity: 'medium',
|
|
139
|
+
confidence: 'suggestion',
|
|
140
|
+
title: 'Missing Strict Mode (set -euo pipefail)',
|
|
141
|
+
description: 'Without strict mode, scripts continue after errors, potentially executing in an inconsistent state.',
|
|
142
|
+
fix: { suggestion: 'Add "set -euo pipefail" near the top of the script for fail-fast behavior.' },
|
|
143
|
+
check({ files }) {
|
|
144
|
+
const findings = [];
|
|
145
|
+
for (const [path, content] of files) {
|
|
146
|
+
if (SKIP_PATH.test(path)) continue;
|
|
147
|
+
if (!isShell(path)) continue;
|
|
148
|
+
if (content.length > 200 && !/set\s+-[euox]*e[uox]*/.test(content) && !/set\s+-euo\s+pipefail/.test(content)) {
|
|
149
|
+
findings.push({
|
|
150
|
+
ruleId: this.id,
|
|
151
|
+
category: this.category,
|
|
152
|
+
severity: this.severity,
|
|
153
|
+
title: this.title,
|
|
154
|
+
description: this.description,
|
|
155
|
+
confidence: this.confidence,
|
|
156
|
+
file: path,
|
|
157
|
+
line: 1,
|
|
158
|
+
fix: this.fix || null,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return findings;
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// SEC-SHELL-007: Temp file race condition
|
|
167
|
+
{
|
|
168
|
+
id: 'SEC-SHELL-007',
|
|
169
|
+
category: 'security',
|
|
170
|
+
severity: 'high',
|
|
171
|
+
confidence: 'likely',
|
|
172
|
+
title: 'Insecure Temporary File Creation',
|
|
173
|
+
description: 'Using $$ or predictable names for temp files creates race condition vulnerabilities (symlink attacks).',
|
|
174
|
+
fix: { suggestion: 'Use mktemp to create temporary files securely: tmpfile=$(mktemp)' },
|
|
175
|
+
check({ files }) {
|
|
176
|
+
const findings = [];
|
|
177
|
+
const pattern = /\/tmp\/.*\$\$|\/tmp\/\w+\.\w+(?!\s*=\s*\$\(mktemp)/;
|
|
178
|
+
for (const [path, content] of files) {
|
|
179
|
+
if (SKIP_PATH.test(path)) continue;
|
|
180
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
181
|
+
}
|
|
182
|
+
return findings;
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// SEC-SHELL-008: Insecure wget/curl (--no-check-certificate)
|
|
187
|
+
{
|
|
188
|
+
id: 'SEC-SHELL-008',
|
|
189
|
+
category: 'security',
|
|
190
|
+
severity: 'high',
|
|
191
|
+
confidence: 'likely',
|
|
192
|
+
title: 'Disabled Certificate Verification',
|
|
193
|
+
description: 'Disabling certificate checks allows man-in-the-middle attacks during downloads.',
|
|
194
|
+
fix: { suggestion: 'Remove --no-check-certificate / --insecure flags. Fix certificate issues properly.' },
|
|
195
|
+
check({ files }) {
|
|
196
|
+
const findings = [];
|
|
197
|
+
const pattern = /--no-check-certificate|curl\s+.*--insecure|-k\s+https?:/;
|
|
198
|
+
for (const [path, content] of files) {
|
|
199
|
+
if (SKIP_PATH.test(path)) continue;
|
|
200
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
201
|
+
}
|
|
202
|
+
return findings;
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// SEC-SHELL-009: World-readable sensitive files
|
|
207
|
+
{
|
|
208
|
+
id: 'SEC-SHELL-009',
|
|
209
|
+
category: 'security',
|
|
210
|
+
severity: 'high',
|
|
211
|
+
confidence: 'likely',
|
|
212
|
+
title: 'World-Readable Sensitive File',
|
|
213
|
+
description: 'Creating sensitive files (keys, configs, credentials) without restricting permissions.',
|
|
214
|
+
fix: { suggestion: 'Set umask 077 before creating sensitive files, or chmod 600 immediately after creation.' },
|
|
215
|
+
check({ files }) {
|
|
216
|
+
const findings = [];
|
|
217
|
+
const pattern = />\s*.*(?:\.pem|\.key|\.crt|\.env|credentials|\.secret|\.password|id_rsa)/;
|
|
218
|
+
for (const [path, content] of files) {
|
|
219
|
+
if (SKIP_PATH.test(path)) continue;
|
|
220
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
221
|
+
}
|
|
222
|
+
return findings;
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// SEC-SHELL-010: Missing input validation
|
|
227
|
+
{
|
|
228
|
+
id: 'SEC-SHELL-010',
|
|
229
|
+
category: 'security',
|
|
230
|
+
severity: 'medium',
|
|
231
|
+
confidence: 'suggestion',
|
|
232
|
+
title: 'Missing Input Validation',
|
|
233
|
+
description: 'Script arguments ($1, $2, etc.) used directly without validation can lead to injection.',
|
|
234
|
+
fix: { suggestion: 'Validate script arguments against expected patterns before using them in commands.' },
|
|
235
|
+
check({ files }) {
|
|
236
|
+
const findings = [];
|
|
237
|
+
const pattern = /(?:rm|cp|mv|cat|chmod|chown|mkdir)\s+.*\$[1-9]/;
|
|
238
|
+
for (const [path, content] of files) {
|
|
239
|
+
if (SKIP_PATH.test(path)) continue;
|
|
240
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
241
|
+
}
|
|
242
|
+
return findings;
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// SEC-SHELL-011: SQL injection in shell
|
|
247
|
+
{
|
|
248
|
+
id: 'SEC-SHELL-011',
|
|
249
|
+
category: 'security',
|
|
250
|
+
severity: 'critical',
|
|
251
|
+
confidence: 'likely',
|
|
252
|
+
title: 'SQL Injection in Shell Script',
|
|
253
|
+
description: 'Interpolating variables into SQL commands allows SQL injection.',
|
|
254
|
+
fix: { suggestion: 'Use parameterized queries or properly escape variables with database-specific escaping.' },
|
|
255
|
+
check({ files }) {
|
|
256
|
+
const findings = [];
|
|
257
|
+
const pattern = /(?:mysql|psql|sqlite3)\s+.*(?:["'].*\$\{?[A-Z_a-z]|`.*\$)/;
|
|
258
|
+
for (const [path, content] of files) {
|
|
259
|
+
if (SKIP_PATH.test(path)) continue;
|
|
260
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
261
|
+
}
|
|
262
|
+
return findings;
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// SEC-SHELL-012: Command injection via backticks
|
|
267
|
+
{
|
|
268
|
+
id: 'SEC-SHELL-012',
|
|
269
|
+
category: 'security',
|
|
270
|
+
severity: 'critical',
|
|
271
|
+
confidence: 'likely',
|
|
272
|
+
title: 'Command Injection via Backticks with User Input',
|
|
273
|
+
description: 'Using backticks with user-controlled variables allows arbitrary command execution.',
|
|
274
|
+
fix: { suggestion: 'Use $() instead of backticks, and always quote variables. Prefer parameter expansion over command substitution with user data.' },
|
|
275
|
+
check({ files }) {
|
|
276
|
+
const findings = [];
|
|
277
|
+
const pattern = /`.*\$(?:\{?[1-9]|\{?(?:USER_INPUT|INPUT|ARG|PARAM))/i;
|
|
278
|
+
for (const [path, content] of files) {
|
|
279
|
+
if (SKIP_PATH.test(path)) continue;
|
|
280
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
281
|
+
}
|
|
282
|
+
return findings;
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// SEC-SHELL-013: Insecure SSH
|
|
287
|
+
{
|
|
288
|
+
id: 'SEC-SHELL-013',
|
|
289
|
+
category: 'security',
|
|
290
|
+
severity: 'high',
|
|
291
|
+
confidence: 'likely',
|
|
292
|
+
title: 'Disabled SSH Host Key Checking',
|
|
293
|
+
description: 'StrictHostKeyChecking=no disables SSH host verification, enabling MITM attacks.',
|
|
294
|
+
fix: { suggestion: 'Remove StrictHostKeyChecking=no and properly manage known_hosts file.' },
|
|
295
|
+
check({ files }) {
|
|
296
|
+
const findings = [];
|
|
297
|
+
const pattern = /StrictHostKeyChecking\s*=?\s*no|StrictHostKeyChecking\s*no|-o\s+StrictHostKeyChecking=no/;
|
|
298
|
+
for (const [path, content] of files) {
|
|
299
|
+
if (SKIP_PATH.test(path)) continue;
|
|
300
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
301
|
+
}
|
|
302
|
+
return findings;
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// SEC-SHELL-014: Storing secrets in history
|
|
307
|
+
{
|
|
308
|
+
id: 'SEC-SHELL-014',
|
|
309
|
+
category: 'security',
|
|
310
|
+
severity: 'high',
|
|
311
|
+
confidence: 'likely',
|
|
312
|
+
title: 'Secrets Exposed in Shell History',
|
|
313
|
+
description: 'Commands with inline passwords or tokens are stored in shell history files.',
|
|
314
|
+
fix: { suggestion: 'Use environment variables or read from stdin. Prefix commands with a space to avoid history (requires HISTCONTROL=ignorespace).' },
|
|
315
|
+
check({ files }) {
|
|
316
|
+
const findings = [];
|
|
317
|
+
const pattern = /(?:mysql|psql|curl|wget)\s+.*(?:-p\s*\S+|--password[= ]\S+|Authorization:\s*Bearer\s+\S{10,})/;
|
|
318
|
+
for (const [path, content] of files) {
|
|
319
|
+
if (SKIP_PATH.test(path)) continue;
|
|
320
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
321
|
+
}
|
|
322
|
+
return findings;
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
// SEC-SHELL-015: Running as root unnecessarily
|
|
327
|
+
{
|
|
328
|
+
id: 'SEC-SHELL-015',
|
|
329
|
+
category: 'security',
|
|
330
|
+
severity: 'medium',
|
|
331
|
+
confidence: 'suggestion',
|
|
332
|
+
title: 'Unnecessary Root Execution',
|
|
333
|
+
description: 'Running entire scripts as root increases the impact of any vulnerability.',
|
|
334
|
+
fix: { suggestion: 'Use sudo only for specific commands that need elevated privileges, not for the entire script.' },
|
|
335
|
+
check({ files }) {
|
|
336
|
+
const findings = [];
|
|
337
|
+
const pattern = /sudo\s+(?:bash|sh|zsh)\s|sudo\s+\.\/|#.*run\s+as\s+root/i;
|
|
338
|
+
for (const [path, content] of files) {
|
|
339
|
+
if (SKIP_PATH.test(path)) continue;
|
|
340
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
341
|
+
}
|
|
342
|
+
return findings;
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// SEC-SHELL-016: SUID bit setting
|
|
347
|
+
{
|
|
348
|
+
id: 'SEC-SHELL-016',
|
|
349
|
+
category: 'security',
|
|
350
|
+
severity: 'critical',
|
|
351
|
+
confidence: 'likely',
|
|
352
|
+
title: 'SUID/SGID Bit Setting',
|
|
353
|
+
description: 'Setting SUID/SGID bits on scripts or binaries can lead to privilege escalation.',
|
|
354
|
+
fix: { suggestion: 'Avoid SUID/SGID on shell scripts. Use sudo with fine-grained sudoers rules instead.' },
|
|
355
|
+
check({ files }) {
|
|
356
|
+
const findings = [];
|
|
357
|
+
const pattern = /chmod\s+[u+]*[46][0-7]{3}\b|chmod\s+[ug]\+s/;
|
|
358
|
+
for (const [path, content] of files) {
|
|
359
|
+
if (SKIP_PATH.test(path)) continue;
|
|
360
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
361
|
+
}
|
|
362
|
+
return findings;
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// SEC-SHELL-017: Insecure PATH manipulation
|
|
367
|
+
{
|
|
368
|
+
id: 'SEC-SHELL-017',
|
|
369
|
+
category: 'security',
|
|
370
|
+
severity: 'high',
|
|
371
|
+
confidence: 'likely',
|
|
372
|
+
title: 'Insecure PATH Manipulation',
|
|
373
|
+
description: 'Adding writable directories (like .) to PATH allows execution of trojan commands.',
|
|
374
|
+
fix: { suggestion: 'Never add current directory (.) to PATH. Use absolute paths for commands in scripts.' },
|
|
375
|
+
check({ files }) {
|
|
376
|
+
const findings = [];
|
|
377
|
+
const pattern = /PATH\s*=.*(?:^|\:)\.(?:\:|$)|export\s+PATH.*\.\:/;
|
|
378
|
+
for (const [path, content] of files) {
|
|
379
|
+
if (SKIP_PATH.test(path)) continue;
|
|
380
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
381
|
+
}
|
|
382
|
+
return findings;
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// SEC-SHELL-018: Unprotected credentials file
|
|
387
|
+
{
|
|
388
|
+
id: 'SEC-SHELL-018',
|
|
389
|
+
category: 'security',
|
|
390
|
+
severity: 'high',
|
|
391
|
+
confidence: 'likely',
|
|
392
|
+
title: 'Sourcing Credentials File Without Permission Check',
|
|
393
|
+
description: 'Sourcing a credentials file without verifying its permissions or ownership could load tampered data.',
|
|
394
|
+
fix: { suggestion: 'Check file ownership and permissions before sourcing: verify owner is current user and mode is 600.' },
|
|
395
|
+
check({ files }) {
|
|
396
|
+
const findings = [];
|
|
397
|
+
const pattern = /(?:source|\.) .*(?:\.env|credentials|secrets|\.secret|\.password)/;
|
|
398
|
+
for (const [path, content] of files) {
|
|
399
|
+
if (SKIP_PATH.test(path)) continue;
|
|
400
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
401
|
+
}
|
|
402
|
+
return findings;
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
// SEC-SHELL-019: Wildcard injection
|
|
407
|
+
{
|
|
408
|
+
id: 'SEC-SHELL-019',
|
|
409
|
+
category: 'security',
|
|
410
|
+
severity: 'high',
|
|
411
|
+
confidence: 'likely',
|
|
412
|
+
title: 'Wildcard Injection Vulnerability',
|
|
413
|
+
description: 'Commands like tar, rsync, or chown with wildcards (*) can be exploited by creating specially named files.',
|
|
414
|
+
fix: { suggestion: 'Use -- to separate options from arguments, or use find -exec instead of wildcards.' },
|
|
415
|
+
check({ files }) {
|
|
416
|
+
const findings = [];
|
|
417
|
+
const pattern = /(?:tar|rsync|chown|chmod|rm)\s+.*\*/;
|
|
418
|
+
for (const [path, content] of files) {
|
|
419
|
+
if (SKIP_PATH.test(path)) continue;
|
|
420
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
421
|
+
}
|
|
422
|
+
return findings;
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// SEC-SHELL-020: Insecure file download
|
|
427
|
+
{
|
|
428
|
+
id: 'SEC-SHELL-020',
|
|
429
|
+
category: 'security',
|
|
430
|
+
severity: 'high',
|
|
431
|
+
confidence: 'likely',
|
|
432
|
+
title: 'Insecure File Download Without Verification',
|
|
433
|
+
description: 'Downloading files without verifying checksums or signatures allows tampered content.',
|
|
434
|
+
fix: { suggestion: 'Verify downloaded files with sha256sum or gpg signature verification.' },
|
|
435
|
+
check({ files }) {
|
|
436
|
+
const findings = [];
|
|
437
|
+
const pattern = /(?:curl|wget)\s+.*-[oO]\s+.*&&\s*(?:chmod|bash|sh|\.\/)/;
|
|
438
|
+
for (const [path, content] of files) {
|
|
439
|
+
if (SKIP_PATH.test(path)) continue;
|
|
440
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
441
|
+
}
|
|
442
|
+
return findings;
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
// SEC-SHELL-021: Unsafe string comparison
|
|
447
|
+
{
|
|
448
|
+
id: 'SEC-SHELL-021',
|
|
449
|
+
category: 'security',
|
|
450
|
+
severity: 'medium',
|
|
451
|
+
confidence: 'suggestion',
|
|
452
|
+
title: 'Unsafe String Comparison',
|
|
453
|
+
description: 'Using [ instead of [[ for string comparison is vulnerable to word splitting and glob expansion.',
|
|
454
|
+
fix: { suggestion: 'Use [[ ]] for string comparisons in bash scripts to avoid word splitting issues.' },
|
|
455
|
+
check({ files }) {
|
|
456
|
+
const findings = [];
|
|
457
|
+
const pattern = /\[\s+\$\w+\s*[!=]/;
|
|
458
|
+
for (const [path, content] of files) {
|
|
459
|
+
if (SKIP_PATH.test(path)) continue;
|
|
460
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
461
|
+
}
|
|
462
|
+
return findings;
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
// SEC-SHELL-022: Exposed AWS credentials
|
|
467
|
+
{
|
|
468
|
+
id: 'SEC-SHELL-022',
|
|
469
|
+
category: 'security',
|
|
470
|
+
severity: 'critical',
|
|
471
|
+
confidence: 'likely',
|
|
472
|
+
title: 'Exposed AWS Credentials',
|
|
473
|
+
description: 'AWS access keys hardcoded in shell scripts can be used to compromise cloud resources.',
|
|
474
|
+
fix: { suggestion: 'Use IAM roles, AWS SSO, or environment variables from a secrets manager.' },
|
|
475
|
+
check({ files }) {
|
|
476
|
+
const findings = [];
|
|
477
|
+
const pattern = /AWS_(?:ACCESS_KEY_ID|SECRET_ACCESS_KEY)\s*=\s*['"][A-Za-z0-9\/+=]{16,}/;
|
|
478
|
+
for (const [path, content] of files) {
|
|
479
|
+
if (SKIP_PATH.test(path)) continue;
|
|
480
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
481
|
+
}
|
|
482
|
+
return findings;
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
// SEC-SHELL-023: Insecure network daemon
|
|
487
|
+
{
|
|
488
|
+
id: 'SEC-SHELL-023',
|
|
489
|
+
category: 'security',
|
|
490
|
+
severity: 'high',
|
|
491
|
+
confidence: 'likely',
|
|
492
|
+
title: 'Insecure Network Listener',
|
|
493
|
+
description: 'Using nc/netcat or socat to create network listeners can expose the system to attacks.',
|
|
494
|
+
fix: { suggestion: 'Use proper services with authentication and encryption. Bind to localhost if only local access is needed.' },
|
|
495
|
+
check({ files }) {
|
|
496
|
+
const findings = [];
|
|
497
|
+
const pattern = /(?:nc|netcat|ncat)\s+.*-l|socat\s+.*TCP-LISTEN/;
|
|
498
|
+
for (const [path, content] of files) {
|
|
499
|
+
if (SKIP_PATH.test(path)) continue;
|
|
500
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
501
|
+
}
|
|
502
|
+
return findings;
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
// SEC-SHELL-024: Disabling firewall
|
|
507
|
+
{
|
|
508
|
+
id: 'SEC-SHELL-024',
|
|
509
|
+
category: 'security',
|
|
510
|
+
severity: 'critical',
|
|
511
|
+
confidence: 'likely',
|
|
512
|
+
title: 'Firewall Disabled',
|
|
513
|
+
description: 'Disabling firewall rules removes a critical security layer.',
|
|
514
|
+
fix: { suggestion: 'Add specific firewall rules instead of disabling the entire firewall.' },
|
|
515
|
+
check({ files }) {
|
|
516
|
+
const findings = [];
|
|
517
|
+
const pattern = /ufw\s+disable|iptables\s+-F|firewall-cmd\s+.*--disable|systemctl\s+stop\s+firewalld/;
|
|
518
|
+
for (const [path, content] of files) {
|
|
519
|
+
if (SKIP_PATH.test(path)) continue;
|
|
520
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
521
|
+
}
|
|
522
|
+
return findings;
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
// SEC-SHELL-025: Disabling SELinux
|
|
527
|
+
{
|
|
528
|
+
id: 'SEC-SHELL-025',
|
|
529
|
+
category: 'security',
|
|
530
|
+
severity: 'high',
|
|
531
|
+
confidence: 'likely',
|
|
532
|
+
title: 'SELinux/AppArmor Disabled',
|
|
533
|
+
description: 'Disabling mandatory access control removes an important security boundary.',
|
|
534
|
+
fix: { suggestion: 'Create proper SELinux policies instead of disabling it. Use semanage/audit2allow.' },
|
|
535
|
+
check({ files }) {
|
|
536
|
+
const findings = [];
|
|
537
|
+
const pattern = /setenforce\s+0|SELINUX\s*=\s*disabled|aa-disable|apparmor_parser\s+-R/;
|
|
538
|
+
for (const [path, content] of files) {
|
|
539
|
+
if (SKIP_PATH.test(path)) continue;
|
|
540
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
541
|
+
}
|
|
542
|
+
return findings;
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// SEC-SHELL-026: Base64 decoded execution
|
|
547
|
+
{
|
|
548
|
+
id: 'SEC-SHELL-026',
|
|
549
|
+
category: 'security',
|
|
550
|
+
severity: 'critical',
|
|
551
|
+
confidence: 'likely',
|
|
552
|
+
title: 'Obfuscated Command Execution',
|
|
553
|
+
description: 'Decoding base64 and piping to shell is a common technique to hide malicious commands.',
|
|
554
|
+
fix: { suggestion: 'Avoid base64-encoding commands. Use clear, readable scripts that can be audited.' },
|
|
555
|
+
check({ files }) {
|
|
556
|
+
const findings = [];
|
|
557
|
+
const pattern = /base64\s+(?:-d|--decode).*\|\s*(?:bash|sh|eval)/;
|
|
558
|
+
for (const [path, content] of files) {
|
|
559
|
+
if (SKIP_PATH.test(path)) continue;
|
|
560
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
561
|
+
}
|
|
562
|
+
return findings;
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// SEC-SHELL-027: Insecure cron job
|
|
567
|
+
{
|
|
568
|
+
id: 'SEC-SHELL-027',
|
|
569
|
+
category: 'security',
|
|
570
|
+
severity: 'medium',
|
|
571
|
+
confidence: 'suggestion',
|
|
572
|
+
title: 'Insecure Cron Job Setup',
|
|
573
|
+
description: 'Adding cron jobs with world-writable scripts or without proper path restrictions.',
|
|
574
|
+
fix: { suggestion: 'Ensure cron scripts are owned by root and not world-writable. Use full paths in cron entries.' },
|
|
575
|
+
check({ files }) {
|
|
576
|
+
const findings = [];
|
|
577
|
+
const pattern = /crontab|\/etc\/cron/;
|
|
578
|
+
for (const [path, content] of files) {
|
|
579
|
+
if (SKIP_PATH.test(path)) continue;
|
|
580
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
581
|
+
}
|
|
582
|
+
return findings;
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
// SEC-SHELL-028: Insecure umask
|
|
587
|
+
{
|
|
588
|
+
id: 'SEC-SHELL-028',
|
|
589
|
+
category: 'security',
|
|
590
|
+
severity: 'medium',
|
|
591
|
+
confidence: 'likely',
|
|
592
|
+
title: 'Permissive Umask',
|
|
593
|
+
description: 'umask 000 or 002 creates files readable/writable by other users.',
|
|
594
|
+
fix: { suggestion: 'Use umask 077 for sensitive operations to create files only accessible by the owner.' },
|
|
595
|
+
check({ files }) {
|
|
596
|
+
const findings = [];
|
|
597
|
+
const pattern = /umask\s+00[0-2]/;
|
|
598
|
+
for (const [path, content] of files) {
|
|
599
|
+
if (SKIP_PATH.test(path)) continue;
|
|
600
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
601
|
+
}
|
|
602
|
+
return findings;
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
// SEC-SHELL-029: Insecure SSH key generation
|
|
607
|
+
{
|
|
608
|
+
id: 'SEC-SHELL-029',
|
|
609
|
+
category: 'security',
|
|
610
|
+
severity: 'high',
|
|
611
|
+
confidence: 'likely',
|
|
612
|
+
title: 'Weak SSH Key Generation',
|
|
613
|
+
description: 'Generating SSH keys with weak algorithms (DSA, RSA < 2048) or without passphrase.',
|
|
614
|
+
fix: { suggestion: 'Use ssh-keygen -t ed25519 or -t rsa -b 4096 with a passphrase.' },
|
|
615
|
+
check({ files }) {
|
|
616
|
+
const findings = [];
|
|
617
|
+
const pattern = /ssh-keygen.*(?:-t\s+dsa|-b\s+(?:512|1024)\b|-N\s*['"]?['"]?\s)/;
|
|
618
|
+
for (const [path, content] of files) {
|
|
619
|
+
if (SKIP_PATH.test(path)) continue;
|
|
620
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
621
|
+
}
|
|
622
|
+
return findings;
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
// SEC-SHELL-030: Writable by others in /etc
|
|
627
|
+
{
|
|
628
|
+
id: 'SEC-SHELL-030',
|
|
629
|
+
category: 'security',
|
|
630
|
+
severity: 'high',
|
|
631
|
+
confidence: 'likely',
|
|
632
|
+
title: 'Writing to System Configuration Without Backup',
|
|
633
|
+
description: 'Modifying files in /etc/ without creating backups or verifying content integrity.',
|
|
634
|
+
fix: { suggestion: 'Create backups before modifying system files. Use configuration management tools (Ansible, Chef) instead.' },
|
|
635
|
+
check({ files }) {
|
|
636
|
+
const findings = [];
|
|
637
|
+
const pattern = /(?:echo|cat|tee|sed\s+-i)\s+.*>.*\/etc\//;
|
|
638
|
+
for (const [path, content] of files) {
|
|
639
|
+
if (SKIP_PATH.test(path)) continue;
|
|
640
|
+
if (isShell(path)) findings.push(...scanLines(content, pattern, path, this));
|
|
641
|
+
}
|
|
642
|
+
return findings;
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
|
|
647
|
+
export default rules;
|