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
package/src/metrics.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const METRICS_DIR = '.doorman';
|
|
5
|
+
const METRICS_FILE = 'metrics.json';
|
|
6
|
+
|
|
7
|
+
function getMetricsPath(targetPath) {
|
|
8
|
+
return join(targetPath, METRICS_DIR, METRICS_FILE);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function ensureDir(targetPath) {
|
|
12
|
+
const dir = join(targetPath, METRICS_DIR);
|
|
13
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadMetrics(targetPath) {
|
|
17
|
+
const path = getMetricsPath(targetPath);
|
|
18
|
+
if (!existsSync(path)) {
|
|
19
|
+
return { scans: [], fixes: [], scores: [] };
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function recordScan(targetPath, { score, findings, stack }) {
|
|
25
|
+
ensureDir(targetPath);
|
|
26
|
+
const metrics = loadMetrics(targetPath);
|
|
27
|
+
|
|
28
|
+
const scan = {
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
score,
|
|
31
|
+
totalFindings: findings.length,
|
|
32
|
+
bySeverity: {
|
|
33
|
+
critical: findings.filter(f => f.severity === 'critical').length,
|
|
34
|
+
high: findings.filter(f => f.severity === 'high').length,
|
|
35
|
+
medium: findings.filter(f => f.severity === 'medium').length,
|
|
36
|
+
low: findings.filter(f => f.severity === 'low').length,
|
|
37
|
+
},
|
|
38
|
+
byCategory: {},
|
|
39
|
+
fixable: findings.filter(f => f.fix).length,
|
|
40
|
+
stack: stack.framework || stack.language || 'unknown',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Count by category
|
|
44
|
+
for (const f of findings) {
|
|
45
|
+
scan.byCategory[f.category] = (scan.byCategory[f.category] || 0) + 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
metrics.scans.push(scan);
|
|
49
|
+
|
|
50
|
+
// Keep last 100 scans
|
|
51
|
+
if (metrics.scans.length > 100) metrics.scans = metrics.scans.slice(-100);
|
|
52
|
+
|
|
53
|
+
// Track score history
|
|
54
|
+
metrics.scores.push({ timestamp: scan.timestamp, score });
|
|
55
|
+
if (metrics.scores.length > 100) metrics.scores = metrics.scores.slice(-100);
|
|
56
|
+
|
|
57
|
+
writeFileSync(getMetricsPath(targetPath), JSON.stringify(metrics, null, 2));
|
|
58
|
+
return scan;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function recordFix(targetPath, { applied, offered, accepted }) {
|
|
62
|
+
ensureDir(targetPath);
|
|
63
|
+
const metrics = loadMetrics(targetPath);
|
|
64
|
+
|
|
65
|
+
metrics.fixes.push({
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
offered,
|
|
68
|
+
applied,
|
|
69
|
+
accepted,
|
|
70
|
+
rate: offered > 0 ? Math.round((accepted / offered) * 100) : 0,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (metrics.fixes.length > 100) metrics.fixes = metrics.fixes.slice(-100);
|
|
74
|
+
|
|
75
|
+
writeFileSync(getMetricsPath(targetPath), JSON.stringify(metrics, null, 2));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getScoreHistory(targetPath) {
|
|
79
|
+
const metrics = loadMetrics(targetPath);
|
|
80
|
+
return metrics.scores;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getFixAcceptanceRate(targetPath) {
|
|
84
|
+
const metrics = loadMetrics(targetPath);
|
|
85
|
+
if (metrics.fixes.length === 0) return null;
|
|
86
|
+
|
|
87
|
+
const totalOffered = metrics.fixes.reduce((sum, f) => sum + f.offered, 0);
|
|
88
|
+
const totalAccepted = metrics.fixes.reduce((sum, f) => sum + f.accepted, 0);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
totalOffered,
|
|
92
|
+
totalAccepted,
|
|
93
|
+
rate: totalOffered > 0 ? Math.round((totalAccepted / totalOffered) * 100) : 0,
|
|
94
|
+
history: metrics.fixes,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function printMetricsSummary(targetPath) {
|
|
99
|
+
const metrics = loadMetrics(targetPath);
|
|
100
|
+
|
|
101
|
+
if (metrics.scans.length === 0) {
|
|
102
|
+
console.log(' No scan history yet. Run `doorman check` first.');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const latest = metrics.scans[metrics.scans.length - 1];
|
|
107
|
+
const first = metrics.scans[0];
|
|
108
|
+
const scoreDelta = latest.score - first.score;
|
|
109
|
+
|
|
110
|
+
console.log('');
|
|
111
|
+
console.log(' Doorman Metrics');
|
|
112
|
+
console.log(' ═══════════════════');
|
|
113
|
+
console.log(` Total scans: ${metrics.scans.length}`);
|
|
114
|
+
console.log(` Current score: ${latest.score}/100`);
|
|
115
|
+
if (metrics.scans.length > 1) {
|
|
116
|
+
console.log(` Score trend: ${scoreDelta >= 0 ? '+' : ''}${scoreDelta} since first scan`);
|
|
117
|
+
}
|
|
118
|
+
console.log(` Total findings: ${latest.totalFindings}`);
|
|
119
|
+
console.log(` Auto-fixable: ${latest.fixable}`);
|
|
120
|
+
|
|
121
|
+
if (metrics.fixes.length > 0) {
|
|
122
|
+
const fixRate = getFixAcceptanceRate(targetPath);
|
|
123
|
+
console.log(` Fix acceptance rate: ${fixRate.rate}%`);
|
|
124
|
+
}
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
package/src/monorepo.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Monorepo detection and workspace root discovery
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect monorepo workspaces and return workspace roots.
|
|
7
|
+
* Supports: npm workspaces, yarn, pnpm, turborepo, nx, lerna
|
|
8
|
+
*/
|
|
9
|
+
export function detectWorkspaces(targetPath) {
|
|
10
|
+
const workspaces = [];
|
|
11
|
+
|
|
12
|
+
// npm/yarn workspaces
|
|
13
|
+
const pkgPath = join(targetPath, 'package.json');
|
|
14
|
+
if (existsSync(pkgPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
const ws = pkg.workspaces;
|
|
18
|
+
if (Array.isArray(ws)) {
|
|
19
|
+
workspaces.push(...ws);
|
|
20
|
+
} else if (ws && Array.isArray(ws.packages)) {
|
|
21
|
+
workspaces.push(...ws.packages);
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// pnpm workspaces
|
|
27
|
+
const pnpmWs = join(targetPath, 'pnpm-workspace.yaml');
|
|
28
|
+
if (existsSync(pnpmWs)) {
|
|
29
|
+
try {
|
|
30
|
+
const content = readFileSync(pnpmWs, 'utf-8');
|
|
31
|
+
const matches = content.match(/- ['"]?([^'":\n]+)['"]?/g);
|
|
32
|
+
if (matches) {
|
|
33
|
+
workspaces.push(...matches.map(m => m.replace(/^- ['"]?/, '').replace(/['"]?$/, '')));
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// turborepo
|
|
39
|
+
if (existsSync(join(targetPath, 'turbo.json'))) {
|
|
40
|
+
// turbo uses package.json workspaces — already handled above
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// nx
|
|
44
|
+
if (existsSync(join(targetPath, 'nx.json'))) {
|
|
45
|
+
if (workspaces.length === 0) {
|
|
46
|
+
// Default nx layout
|
|
47
|
+
if (existsSync(join(targetPath, 'apps'))) workspaces.push('apps/*');
|
|
48
|
+
if (existsSync(join(targetPath, 'libs'))) workspaces.push('libs/*');
|
|
49
|
+
if (existsSync(join(targetPath, 'packages'))) workspaces.push('packages/*');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// lerna
|
|
54
|
+
const lernaPath = join(targetPath, 'lerna.json');
|
|
55
|
+
if (existsSync(lernaPath) && workspaces.length === 0) {
|
|
56
|
+
try {
|
|
57
|
+
const lerna = JSON.parse(readFileSync(lernaPath, 'utf-8'));
|
|
58
|
+
if (Array.isArray(lerna.packages)) {
|
|
59
|
+
workspaces.push(...lerna.packages);
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { isMonorepo: workspaces.length > 0, workspaces };
|
|
65
|
+
}
|
package/src/presets.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Built-in configuration presets for Doorman
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* "recommended" — sensible defaults for most projects.
|
|
7
|
+
* All categories, medium+ severity, common ignore patterns.
|
|
8
|
+
*/
|
|
9
|
+
const recommended = {
|
|
10
|
+
rules: {},
|
|
11
|
+
categories: [], // empty = all categories
|
|
12
|
+
severity: 'medium',
|
|
13
|
+
ignore: ['test/**', 'vendor/**', '*.min.js'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* "strict" — everything, no mercy.
|
|
18
|
+
* All categories, all severities, no extra ignore patterns.
|
|
19
|
+
*/
|
|
20
|
+
const strict = {
|
|
21
|
+
rules: {},
|
|
22
|
+
categories: [], // all categories
|
|
23
|
+
severity: 'low',
|
|
24
|
+
ignore: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* "minimal" — only the critical stuff.
|
|
29
|
+
* Security + reliability only, high+ severity.
|
|
30
|
+
*/
|
|
31
|
+
const minimal = {
|
|
32
|
+
rules: {},
|
|
33
|
+
categories: ['security', 'reliability'],
|
|
34
|
+
severity: 'high',
|
|
35
|
+
ignore: ['test/**', 'vendor/**', '*.min.js'],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const PRESETS = { recommended, strict, minimal };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a preset by name. Returns a shallow copy or null if unknown.
|
|
42
|
+
*/
|
|
43
|
+
export function getPreset(name) {
|
|
44
|
+
const preset = PRESETS[name];
|
|
45
|
+
if (!preset) return null;
|
|
46
|
+
return {
|
|
47
|
+
...preset,
|
|
48
|
+
rules: { ...preset.rules },
|
|
49
|
+
categories: [...preset.categories],
|
|
50
|
+
ignore: [...preset.ignore],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { PRESETS };
|