getdoorman 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/bin/doorman.js +444 -0
  4. package/package.json +74 -0
  5. package/src/ai-fixer.js +559 -0
  6. package/src/ast-scanner.js +434 -0
  7. package/src/auth.js +149 -0
  8. package/src/baseline.js +48 -0
  9. package/src/compliance.js +539 -0
  10. package/src/config.js +466 -0
  11. package/src/custom-rules.js +32 -0
  12. package/src/dashboard.js +202 -0
  13. package/src/detector.js +142 -0
  14. package/src/fix-engine.js +48 -0
  15. package/src/fix-registry-extra.js +95 -0
  16. package/src/fix-registry-go-rust.js +77 -0
  17. package/src/fix-registry-java-csharp.js +77 -0
  18. package/src/fix-registry-js.js +99 -0
  19. package/src/fix-registry-mcp-ai.js +57 -0
  20. package/src/fix-registry-python.js +87 -0
  21. package/src/fixer-ruby-php.js +608 -0
  22. package/src/fixer.js +2113 -0
  23. package/src/hooks.js +115 -0
  24. package/src/ignore.js +176 -0
  25. package/src/index.js +384 -0
  26. package/src/metrics.js +126 -0
  27. package/src/monorepo.js +65 -0
  28. package/src/presets.js +54 -0
  29. package/src/reporter.js +975 -0
  30. package/src/rule-worker.js +36 -0
  31. package/src/rules/ast-rules.js +756 -0
  32. package/src/rules/bugs/accessibility.js +235 -0
  33. package/src/rules/bugs/ai-codegen-fixable.js +172 -0
  34. package/src/rules/bugs/ai-codegen.js +365 -0
  35. package/src/rules/bugs/code-smell-bugs.js +247 -0
  36. package/src/rules/bugs/crypto-bugs.js +195 -0
  37. package/src/rules/bugs/docker-bugs.js +158 -0
  38. package/src/rules/bugs/general.js +361 -0
  39. package/src/rules/bugs/go-bugs.js +279 -0
  40. package/src/rules/bugs/index.js +73 -0
  41. package/src/rules/bugs/js-api.js +257 -0
  42. package/src/rules/bugs/js-array-object.js +210 -0
  43. package/src/rules/bugs/js-async-fixable.js +223 -0
  44. package/src/rules/bugs/js-async.js +211 -0
  45. package/src/rules/bugs/js-closure-scope.js +182 -0
  46. package/src/rules/bugs/js-database.js +203 -0
  47. package/src/rules/bugs/js-error-handling.js +148 -0
  48. package/src/rules/bugs/js-logic.js +261 -0
  49. package/src/rules/bugs/js-memory.js +214 -0
  50. package/src/rules/bugs/js-node.js +361 -0
  51. package/src/rules/bugs/js-react.js +373 -0
  52. package/src/rules/bugs/js-regex.js +200 -0
  53. package/src/rules/bugs/js-state.js +272 -0
  54. package/src/rules/bugs/js-type-coercion.js +318 -0
  55. package/src/rules/bugs/nextjs-bugs.js +242 -0
  56. package/src/rules/bugs/nextjs-fixable.js +120 -0
  57. package/src/rules/bugs/node-fixable.js +178 -0
  58. package/src/rules/bugs/python-advanced.js +245 -0
  59. package/src/rules/bugs/python-fixable.js +98 -0
  60. package/src/rules/bugs/python.js +284 -0
  61. package/src/rules/bugs/react-fixable.js +207 -0
  62. package/src/rules/bugs/ruby-bugs.js +182 -0
  63. package/src/rules/bugs/shell-bugs.js +181 -0
  64. package/src/rules/bugs/silent-failures.js +261 -0
  65. package/src/rules/bugs/ts-bugs.js +235 -0
  66. package/src/rules/bugs/unused-vars.js +65 -0
  67. package/src/rules/compliance/accessibility-ext.js +468 -0
  68. package/src/rules/compliance/education.js +322 -0
  69. package/src/rules/compliance/financial.js +421 -0
  70. package/src/rules/compliance/frameworks.js +507 -0
  71. package/src/rules/compliance/healthcare.js +520 -0
  72. package/src/rules/compliance/index.js +2714 -0
  73. package/src/rules/compliance/regional-eu.js +480 -0
  74. package/src/rules/compliance/regional-international.js +903 -0
  75. package/src/rules/cost/index.js +1993 -0
  76. package/src/rules/data/index.js +2503 -0
  77. package/src/rules/dependencies/index.js +1684 -0
  78. package/src/rules/deployment/index.js +2050 -0
  79. package/src/rules/index.js +71 -0
  80. package/src/rules/infrastructure/index.js +3048 -0
  81. package/src/rules/performance/index.js +3455 -0
  82. package/src/rules/quality/index.js +3175 -0
  83. package/src/rules/reliability/index.js +3040 -0
  84. package/src/rules/scope-rules.js +815 -0
  85. package/src/rules/security/ai-api.js +1177 -0
  86. package/src/rules/security/auth.js +1328 -0
  87. package/src/rules/security/cors.js +127 -0
  88. package/src/rules/security/crypto.js +527 -0
  89. package/src/rules/security/csharp.js +862 -0
  90. package/src/rules/security/csrf.js +193 -0
  91. package/src/rules/security/dart.js +835 -0
  92. package/src/rules/security/deserialization.js +291 -0
  93. package/src/rules/security/file-upload.js +187 -0
  94. package/src/rules/security/go.js +850 -0
  95. package/src/rules/security/headers.js +235 -0
  96. package/src/rules/security/index.js +65 -0
  97. package/src/rules/security/injection.js +1639 -0
  98. package/src/rules/security/mcp-server.js +71 -0
  99. package/src/rules/security/misconfiguration.js +660 -0
  100. package/src/rules/security/oauth-jwt.js +329 -0
  101. package/src/rules/security/path-traversal.js +295 -0
  102. package/src/rules/security/php.js +1054 -0
  103. package/src/rules/security/prototype-pollution.js +283 -0
  104. package/src/rules/security/rate-limiting.js +208 -0
  105. package/src/rules/security/ruby.js +1061 -0
  106. package/src/rules/security/rust.js +693 -0
  107. package/src/rules/security/secrets.js +747 -0
  108. package/src/rules/security/shell.js +647 -0
  109. package/src/rules/security/ssrf.js +298 -0
  110. package/src/rules/security/supply-chain-advanced.js +393 -0
  111. package/src/rules/security/supply-chain.js +734 -0
  112. package/src/rules/security/swift.js +835 -0
  113. package/src/rules/security/taint.js +27 -0
  114. package/src/rules/security/xss.js +520 -0
  115. package/src/scan-cache.js +71 -0
  116. package/src/scanner.js +710 -0
  117. package/src/scope-analyzer.js +685 -0
  118. package/src/share.js +88 -0
  119. package/src/taint.js +300 -0
  120. package/src/telemetry.js +183 -0
  121. package/src/tracer.js +190 -0
  122. package/src/upload.js +35 -0
  123. package/src/worker.js +31 -0
@@ -0,0 +1,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;