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,195 @@
1
+ // Bug detection: Cryptographic and hashing bugs
2
+ const JS_EXT = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'];
3
+ const PY_EXT = ['.py'];
4
+ function isJS(f) { return JS_EXT.some(e => f.endsWith(e)); }
5
+ function isPy(f) { return PY_EXT.some(e => f.endsWith(e)); }
6
+
7
+ const rules = [
8
+ // BUG-CRYPTO-001: Hash truncated too short — collision risk
9
+ {
10
+ id: 'BUG-CRYPTO-001',
11
+ category: 'bugs',
12
+ severity: 'high',
13
+ confidence: 'likely',
14
+ title: 'Hash digest truncated too short — collision risk',
15
+ check({ files }) {
16
+ const findings = [];
17
+ for (const [fp, content] of files) {
18
+ if (!isJS(fp)) continue;
19
+ const lines = content.split('\n');
20
+ for (let i = 0; i < lines.length; i++) {
21
+ const line = lines[i];
22
+ if (line.trim().startsWith('//')) continue;
23
+ // Match: .digest('hex').slice(0, N) or .substring(0, N)
24
+ const match = line.match(/\.digest\s*\(\s*['"]hex['"]\s*\)\s*\.(?:slice|substring)\s*\(\s*0\s*,\s*(\d+)\s*\)/);
25
+ if (match) {
26
+ const len = parseInt(match[1], 10);
27
+ // 16 hex chars = 64 bits = birthday collision at ~2^32
28
+ // 32 hex chars = 128 bits = safe for most uses
29
+ if (len < 32) {
30
+ findings.push({
31
+ ruleId: 'BUG-CRYPTO-001', category: 'bugs', severity: 'high',
32
+ title: `Hash truncated to ${len} hex chars (${len * 4} bits) — collision risk`,
33
+ description: `Truncating a hash to ${len} characters gives only ${len * 4} bits of entropy. Birthday paradox means collisions after ~2^${len * 2} values. Use at least 32 hex chars (128 bits) for identifiers, fingerprints, and cache keys.`,
34
+ file: fp, line: i + 1,
35
+ fix: len < 32 ? {
36
+ type: 'replace',
37
+ old: match[0],
38
+ new: match[0].replace(`(0, ${len})`, '(0, 32)'),
39
+ } : null,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ }
45
+ return findings;
46
+ },
47
+ },
48
+
49
+ // BUG-CRYPTO-002: Python hash truncated too short
50
+ {
51
+ id: 'BUG-CRYPTO-002',
52
+ category: 'bugs',
53
+ severity: 'high',
54
+ confidence: 'likely',
55
+ title: 'Hash digest truncated too short — collision risk',
56
+ check({ files }) {
57
+ const findings = [];
58
+ for (const [fp, content] of files) {
59
+ if (!isPy(fp)) continue;
60
+ const lines = content.split('\n');
61
+ for (let i = 0; i < lines.length; i++) {
62
+ const line = lines[i];
63
+ if (line.trim().startsWith('#')) continue;
64
+ // Match: .hexdigest()[:N]
65
+ const match = line.match(/\.hexdigest\s*\(\s*\)\s*\[\s*:(\d+)\s*\]/);
66
+ if (match) {
67
+ const len = parseInt(match[1], 10);
68
+ if (len < 32) {
69
+ findings.push({
70
+ ruleId: 'BUG-CRYPTO-002', category: 'bugs', severity: 'high',
71
+ title: `Hash truncated to ${len} hex chars (${len * 4} bits) — collision risk`,
72
+ description: `Truncating a hash to ${len} characters gives only ${len * 4} bits of entropy. Use at least 32 hex chars (128 bits) for identifiers and fingerprints.`,
73
+ file: fp, line: i + 1,
74
+ fix: null,
75
+ });
76
+ }
77
+ }
78
+ }
79
+ }
80
+ return findings;
81
+ },
82
+ },
83
+
84
+ // BUG-CRYPTO-003: Using Math.random() for IDs/tokens
85
+ {
86
+ id: 'BUG-CRYPTO-003',
87
+ category: 'bugs',
88
+ severity: 'high',
89
+ confidence: 'likely',
90
+ title: 'Math.random() used for ID/token generation — not cryptographically secure',
91
+ check({ files }) {
92
+ const findings = [];
93
+ for (const [fp, content] of files) {
94
+ if (!isJS(fp)) continue;
95
+ const lines = content.split('\n');
96
+ for (let i = 0; i < lines.length; i++) {
97
+ const line = lines[i];
98
+ if (line.trim().startsWith('//')) continue;
99
+ // Math.random used with toString(36) or in context of id/token/key generation
100
+ if (/Math\.random\(\)/.test(line) &&
101
+ (/toString\s*\(\s*36\s*\)/.test(line) || /(?:id|token|key|uuid|nonce|session|secret)\s*[=:]/i.test(line))) {
102
+ findings.push({
103
+ ruleId: 'BUG-CRYPTO-003', category: 'bugs', severity: 'high',
104
+ title: 'Math.random() for ID/token generation — predictable output',
105
+ description: 'Math.random() is not cryptographically secure and its output is predictable. Use crypto.randomUUID() or crypto.randomBytes() for tokens, IDs, and secrets.',
106
+ file: fp, line: i + 1,
107
+ fix: null,
108
+ });
109
+ }
110
+ }
111
+ }
112
+ return findings;
113
+ },
114
+ },
115
+
116
+ // BUG-CRYPTO-004: Weak hash algorithm (MD5/SHA1) for security purposes
117
+ {
118
+ id: 'BUG-CRYPTO-004',
119
+ category: 'bugs',
120
+ severity: 'high',
121
+ confidence: 'likely',
122
+ title: 'Weak hash algorithm (MD5/SHA1) used for security-sensitive purpose',
123
+ check({ files }) {
124
+ const findings = [];
125
+ for (const [fp, content] of files) {
126
+ if (!isJS(fp) && !isPy(fp)) continue;
127
+ const lines = content.split('\n');
128
+ for (let i = 0; i < lines.length; i++) {
129
+ const line = lines[i];
130
+ if (line.trim().startsWith('//') || line.trim().startsWith('#')) continue;
131
+
132
+ let algo = null;
133
+ // JS: createHash('md5') or createHash('sha1')
134
+ const jsMatch = line.match(/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/i);
135
+ if (jsMatch) algo = jsMatch[0].includes('md5') ? 'MD5' : 'SHA1';
136
+
137
+ // Python: hashlib.md5() or hashlib.sha1()
138
+ const pyMatch = line.match(/hashlib\.(?:md5|sha1)\s*\(/i);
139
+ if (pyMatch) algo = pyMatch[0].includes('md5') ? 'MD5' : 'SHA1';
140
+
141
+ if (algo) {
142
+ // Check context for security use (password, auth, token, verify, sign)
143
+ if (/(?:password|auth|token|verify|sign|secret|credential|session)/i.test(line) ||
144
+ /(?:password|auth|token|verify|sign|secret|credential|session)/i.test(lines[Math.max(0, i - 1)] || '')) {
145
+ findings.push({
146
+ ruleId: 'BUG-CRYPTO-004', category: 'bugs', severity: 'high',
147
+ title: `${algo} used for security-sensitive purpose — cryptographically broken`,
148
+ description: `${algo} has known collision attacks and should not be used for passwords, authentication, or signatures. Use SHA-256 or bcrypt/argon2 for passwords.`,
149
+ file: fp, line: i + 1,
150
+ fix: null,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return findings;
157
+ },
158
+ },
159
+
160
+ // BUG-CRYPTO-005: Hardcoded encryption key/IV
161
+ {
162
+ id: 'BUG-CRYPTO-005',
163
+ category: 'bugs',
164
+ severity: 'critical',
165
+ confidence: 'likely',
166
+ title: 'Hardcoded encryption key or IV',
167
+ check({ files }) {
168
+ const findings = [];
169
+ for (const [fp, content] of files) {
170
+ if (!isJS(fp)) continue;
171
+ const lines = content.split('\n');
172
+ for (let i = 0; i < lines.length; i++) {
173
+ const line = lines[i];
174
+ if (line.trim().startsWith('//')) continue;
175
+ // createCipheriv with string literal key/iv
176
+ if (/createCipher(?:iv)?\s*\(\s*['"]/.test(line)) {
177
+ // Check if key/iv are string literals (not variables from env)
178
+ if (/createCipher(?:iv)?\s*\(\s*['"][^'"]+['"]\s*,\s*['"]/.test(line)) {
179
+ findings.push({
180
+ ruleId: 'BUG-CRYPTO-005', category: 'bugs', severity: 'high',
181
+ title: 'Hardcoded encryption key — anyone with source code can decrypt',
182
+ description: 'Encryption keys must not be hardcoded. Load from environment variables or a secrets manager: process.env.ENCRYPTION_KEY.',
183
+ file: fp, line: i + 1,
184
+ fix: null,
185
+ });
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return findings;
191
+ },
192
+ },
193
+ ];
194
+
195
+ export default rules;
@@ -0,0 +1,158 @@
1
+ // Bug detection: Dockerfile anti-patterns
2
+ function isDockerfile(f) { return f === 'Dockerfile' || f.endsWith('/Dockerfile') || /Dockerfile\.\w+$/.test(f) || f.endsWith('.dockerfile'); }
3
+ function isCompose(f) { return /docker-compose.*\.ya?ml$/.test(f) || /compose\.ya?ml$/.test(f); }
4
+
5
+ const rules = [
6
+ {
7
+ id: 'BUG-DOCKER-001',
8
+ category: 'bugs',
9
+ severity: 'high',
10
+ confidence: 'definite',
11
+ title: 'Running as root in container',
12
+ check({ files }) {
13
+ const findings = [];
14
+ for (const [fp, content] of files) {
15
+ if (!isDockerfile(fp)) continue;
16
+ if (!/\bUSER\b/.test(content)) {
17
+ findings.push({
18
+ ruleId: 'BUG-DOCKER-001', category: 'bugs', severity: 'high',
19
+ title: 'Container runs as root — security risk',
20
+ description: 'Without a USER directive, the container runs as root. Add USER node (or appropriate non-root user) after installing dependencies.',
21
+ file: fp, line: 1, fix: null,
22
+ });
23
+ }
24
+ }
25
+ return findings;
26
+ },
27
+ },
28
+ {
29
+ id: 'BUG-DOCKER-002',
30
+ category: 'bugs',
31
+ severity: 'high',
32
+ confidence: 'definite',
33
+ title: 'Using :latest tag — non-reproducible builds',
34
+ check({ files }) {
35
+ const findings = [];
36
+ for (const [fp, content] of files) {
37
+ if (!isDockerfile(fp)) continue;
38
+ const lines = content.split('\n');
39
+ for (let i = 0; i < lines.length; i++) {
40
+ if (/^FROM\s+\w+(:latest)?\s*$/i.test(lines[i]) || /^FROM\s+\w+:latest\b/.test(lines[i])) {
41
+ findings.push({
42
+ ruleId: 'BUG-DOCKER-002', category: 'bugs', severity: 'high',
43
+ title: 'FROM with :latest tag — builds are not reproducible',
44
+ description: 'Pin to a specific version (e.g., node:20-alpine) so builds are reproducible. :latest can change unexpectedly and break your app.',
45
+ file: fp, line: i + 1, fix: null,
46
+ });
47
+ }
48
+ }
49
+ }
50
+ return findings;
51
+ },
52
+ },
53
+ {
54
+ id: 'BUG-DOCKER-003',
55
+ category: 'bugs',
56
+ severity: 'medium',
57
+ confidence: 'definite',
58
+ title: 'COPY . before npm install — cache invalidation',
59
+ check({ files }) {
60
+ const findings = [];
61
+ for (const [fp, content] of files) {
62
+ if (!isDockerfile(fp)) continue;
63
+ const lines = content.split('\n');
64
+ let sawCopyDot = false;
65
+ let sawInstall = false;
66
+ for (let i = 0; i < lines.length; i++) {
67
+ if (/^COPY\s+\.\s/.test(lines[i]) || /^COPY\s+\.\s*$/.test(lines[i]) || /^ADD\s+\.\s/.test(lines[i])) {
68
+ sawCopyDot = true;
69
+ }
70
+ if (sawCopyDot && /npm install|yarn install|pnpm install|pip install/.test(lines[i])) {
71
+ sawInstall = true;
72
+ findings.push({
73
+ ruleId: 'BUG-DOCKER-003', category: 'bugs', severity: 'medium',
74
+ title: 'COPY . before dependency install — every code change re-downloads deps',
75
+ description: 'Copy package.json first, run install, then COPY the rest. This lets Docker cache the dependency layer.',
76
+ file: fp, line: i + 1, fix: null,
77
+ });
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ return findings;
83
+ },
84
+ },
85
+ {
86
+ id: 'BUG-DOCKER-004',
87
+ category: 'bugs',
88
+ severity: 'medium',
89
+ confidence: 'likely',
90
+ title: 'No .dockerignore — node_modules copied into image',
91
+ check({ files }) {
92
+ const findings = [];
93
+ const hasDockerfile = [...files.keys()].some(f => isDockerfile(f));
94
+ const hasDockerignore = [...files.keys()].some(f => f === '.dockerignore' || f.endsWith('/.dockerignore'));
95
+ if (hasDockerfile && !hasDockerignore) {
96
+ findings.push({
97
+ ruleId: 'BUG-DOCKER-004', category: 'bugs', severity: 'medium',
98
+ title: 'No .dockerignore file — node_modules, .git, etc. copied into image',
99
+ description: 'Without .dockerignore, COPY . sends everything (node_modules, .git, .env) to the daemon. Create .dockerignore with: node_modules, .git, .env, dist.',
100
+ file: 'Dockerfile', line: 1, fix: null,
101
+ });
102
+ }
103
+ return findings;
104
+ },
105
+ },
106
+ {
107
+ id: 'BUG-DOCKER-005',
108
+ category: 'bugs',
109
+ severity: 'high',
110
+ confidence: 'definite',
111
+ title: 'Secrets in Dockerfile — baked into image layer',
112
+ check({ files }) {
113
+ const findings = [];
114
+ for (const [fp, content] of files) {
115
+ if (!isDockerfile(fp)) continue;
116
+ const lines = content.split('\n');
117
+ for (let i = 0; i < lines.length; i++) {
118
+ if (/^ENV\s+\w*(?:SECRET|PASSWORD|TOKEN|API_KEY|PRIVATE_KEY|CREDENTIALS)\w*[\s=]/i.test(lines[i]) && !/\$\{/.test(lines[i])) {
119
+ findings.push({
120
+ ruleId: 'BUG-DOCKER-005', category: 'bugs', severity: 'high',
121
+ title: 'Secret hardcoded in Dockerfile ENV — visible in image layers',
122
+ description: 'ENV values are baked into image layers and visible to anyone with the image. Use runtime env vars (docker run -e) or secrets management.',
123
+ file: fp, line: i + 1, fix: null,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ return findings;
129
+ },
130
+ },
131
+ {
132
+ id: 'BUG-DOCKER-006',
133
+ category: 'bugs',
134
+ severity: 'medium',
135
+ confidence: 'likely',
136
+ title: 'No health check defined',
137
+ check({ files }) {
138
+ const findings = [];
139
+ for (const [fp, content] of files) {
140
+ if (!isDockerfile(fp)) continue;
141
+ if (!/HEALTHCHECK/.test(content)) {
142
+ // Only flag if it's a server (has EXPOSE)
143
+ if (/EXPOSE/.test(content)) {
144
+ findings.push({
145
+ ruleId: 'BUG-DOCKER-006', category: 'bugs', severity: 'medium',
146
+ title: 'No HEALTHCHECK — orchestrator can\'t detect unhealthy containers',
147
+ description: 'Add HEALTHCHECK CMD curl -f http://localhost:PORT/health || exit 1 so Docker/K8s can detect and restart unhealthy containers.',
148
+ file: fp, line: 1, fix: null,
149
+ });
150
+ }
151
+ }
152
+ }
153
+ return findings;
154
+ },
155
+ },
156
+ ];
157
+
158
+ export default rules;