@maverick006/vibeguard 1.0.7 → 1.0.9
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/dist/index.js +122 -143
- package/package.json +1 -1
- package/src/index.ts +124 -145
package/dist/index.js
CHANGED
|
@@ -23,20 +23,54 @@ program
|
|
|
23
23
|
.description('Run a security scan on the current directory or target path')
|
|
24
24
|
.option('-d, --dir <path>', 'Directory to scan', process.cwd())
|
|
25
25
|
.option('--fix', 'Automatically generate AI remediation fixes and interactive diff')
|
|
26
|
-
.option('--
|
|
26
|
+
.option('--ci', 'Run in non-interactive CI mode and exit with policy status code')
|
|
27
27
|
.action(async (targetPath, options) => {
|
|
28
28
|
const scanDir = targetPath || options.dir || process.cwd();
|
|
29
29
|
const startTime = Date.now();
|
|
30
|
+
let hasSystemError = false;
|
|
31
|
+
// Use a minimal spinner if in CI mode, or bypass ora entirely if preferred.
|
|
32
|
+
// For simplicity, we just won't render the interactive dashboard if --ci is set.
|
|
30
33
|
const spinner = (0, ora_1.default)({
|
|
31
34
|
text: chalk_1.default.hex('#00E5FF')('Scanning repository for vulnerabilities (SAST, SCA, Secrets, IaC)...'),
|
|
32
|
-
spinner: 'dots'
|
|
35
|
+
spinner: 'dots',
|
|
36
|
+
isSilent: options.ci
|
|
33
37
|
}).start();
|
|
34
38
|
let findings = [];
|
|
35
39
|
try {
|
|
36
|
-
// Initialize the security orchestrator and
|
|
40
|
+
// Initialize the security orchestrator and scanners
|
|
37
41
|
const { Orchestrator } = require('@maverick006/security-engine');
|
|
38
|
-
const
|
|
39
|
-
|
|
42
|
+
const scanners = [];
|
|
43
|
+
try {
|
|
44
|
+
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
45
|
+
scanners.push(new NpmAuditScanner());
|
|
46
|
+
}
|
|
47
|
+
catch (e) { }
|
|
48
|
+
try {
|
|
49
|
+
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
50
|
+
scanners.push(new TrivyScanner());
|
|
51
|
+
}
|
|
52
|
+
catch (e) { }
|
|
53
|
+
try {
|
|
54
|
+
const { SemgrepScanner } = require('@maverick006/scanner-semgrep');
|
|
55
|
+
scanners.push(new SemgrepScanner());
|
|
56
|
+
}
|
|
57
|
+
catch (e) { }
|
|
58
|
+
try {
|
|
59
|
+
const { GitleaksScanner } = require('@maverick006/scanner-gitleaks');
|
|
60
|
+
scanners.push(new GitleaksScanner());
|
|
61
|
+
}
|
|
62
|
+
catch (e) { }
|
|
63
|
+
try {
|
|
64
|
+
const { CheckovScanner } = require('@maverick006/scanner-checkov');
|
|
65
|
+
scanners.push(new CheckovScanner());
|
|
66
|
+
}
|
|
67
|
+
catch (e) { }
|
|
68
|
+
try {
|
|
69
|
+
const { ZapScanner } = require('@maverick006/scanner-zap');
|
|
70
|
+
scanners.push(new ZapScanner());
|
|
71
|
+
}
|
|
72
|
+
catch (e) { }
|
|
73
|
+
const orchestrator = new Orchestrator(scanners);
|
|
40
74
|
const scanResult = await orchestrator.runScan({
|
|
41
75
|
scanId: `scan-${Date.now()}`,
|
|
42
76
|
repositoryUrl: 'local',
|
|
@@ -46,108 +80,8 @@ program
|
|
|
46
80
|
findings = scanResult.findings || [];
|
|
47
81
|
}
|
|
48
82
|
catch (err) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// If demo mode or no findings found in empty directory, provide rich showcase findings matching design
|
|
52
|
-
if (options.demo || findings.length === 0) {
|
|
53
|
-
findings = [
|
|
54
|
-
{
|
|
55
|
-
id: 'VG-CRIT-001',
|
|
56
|
-
title: 'Hardcoded AWS Secret Key',
|
|
57
|
-
severity: types_1.Severity.CRITICAL,
|
|
58
|
-
scanner: 'Gitleaks',
|
|
59
|
-
file: 'config/aws.py',
|
|
60
|
-
line: 12,
|
|
61
|
-
description: 'AWS Secret Key is hardcoded in the source code.',
|
|
62
|
-
codeSnippet: 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"'
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'VG-CRIT-002',
|
|
66
|
-
title: 'Exposed JWT Secret',
|
|
67
|
-
severity: types_1.Severity.CRITICAL,
|
|
68
|
-
scanner: 'Gitleaks',
|
|
69
|
-
file: 'config/auth.py',
|
|
70
|
-
line: 8,
|
|
71
|
-
description: 'Hardcoded JWT secret token detected.',
|
|
72
|
-
codeSnippet: 'const JWT_SECRET = "super_secret_123"'
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: 'VG-HIGH-003',
|
|
76
|
-
title: 'SQL Injection Risk',
|
|
77
|
-
severity: types_1.Severity.HIGH,
|
|
78
|
-
scanner: 'Semgrep',
|
|
79
|
-
file: 'api/users.py',
|
|
80
|
-
line: 45,
|
|
81
|
-
description: 'Direct concatenation of user input into SQL query.',
|
|
82
|
-
codeSnippet: 'query = "SELECT * FROM users WHERE name = " + req.query.name'
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
id: 'VG-HIGH-004',
|
|
86
|
-
title: 'Outdated Dependency (lodash)',
|
|
87
|
-
severity: types_1.Severity.HIGH,
|
|
88
|
-
scanner: 'npm-audit',
|
|
89
|
-
file: 'package.json',
|
|
90
|
-
line: 23,
|
|
91
|
-
description: 'Vulnerable prototype pollution in lodash version.',
|
|
92
|
-
codeSnippet: '"lodash": "4.17.15"'
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
id: 'VG-HIGH-005',
|
|
96
|
-
title: 'Unsafe Deserialization',
|
|
97
|
-
severity: types_1.Severity.HIGH,
|
|
98
|
-
scanner: 'Semgrep',
|
|
99
|
-
file: 'utils/parser.py',
|
|
100
|
-
line: 78,
|
|
101
|
-
description: 'Unsafe pickle.loads execution.',
|
|
102
|
-
codeSnippet: 'data = pickle.loads(user_input)'
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: 'VG-MED-006',
|
|
106
|
-
title: 'S3 Bucket Public Read',
|
|
107
|
-
severity: types_1.Severity.MEDIUM,
|
|
108
|
-
scanner: 'Checkov',
|
|
109
|
-
file: 'iac/s3.tf',
|
|
110
|
-
line: 14,
|
|
111
|
-
description: 'Public read access enabled on production storage bucket.',
|
|
112
|
-
codeSnippet: 'acl = "public-read"'
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: 'VG-MED-007',
|
|
116
|
-
title: 'Missing Rate Limiting',
|
|
117
|
-
severity: types_1.Severity.MEDIUM,
|
|
118
|
-
scanner: 'Semgrep',
|
|
119
|
-
file: 'api/auth.py',
|
|
120
|
-
line: 32,
|
|
121
|
-
description: 'Authentication endpoint lacks rate limiting protection.'
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
id: 'VG-MED-008',
|
|
125
|
-
title: 'CORS Misconfiguration',
|
|
126
|
-
severity: types_1.Severity.MEDIUM,
|
|
127
|
-
scanner: 'Semgrep',
|
|
128
|
-
file: 'web/middleware.py',
|
|
129
|
-
line: 19,
|
|
130
|
-
description: 'Wildcard CORS origin enabled in production middleware.'
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
id: 'VG-LOW-009',
|
|
134
|
-
title: 'Unused Dependency',
|
|
135
|
-
severity: types_1.Severity.LOW,
|
|
136
|
-
scanner: 'npm-audit',
|
|
137
|
-
file: 'package.json',
|
|
138
|
-
line: 102,
|
|
139
|
-
description: 'Unused package detected.'
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
id: 'VG-LOW-010',
|
|
143
|
-
title: 'Missing Security Headers',
|
|
144
|
-
severity: types_1.Severity.LOW,
|
|
145
|
-
scanner: 'Zap',
|
|
146
|
-
file: 'web/server.py',
|
|
147
|
-
line: 56,
|
|
148
|
-
description: 'Strict-Transport-Security header is not set.'
|
|
149
|
-
}
|
|
150
|
-
];
|
|
83
|
+
console.error(chalk_1.default.red('Orchestrator failed to run scans.'), err);
|
|
84
|
+
hasSystemError = true;
|
|
151
85
|
}
|
|
152
86
|
spinner.stop();
|
|
153
87
|
const elapsedMs = Date.now() - startTime;
|
|
@@ -157,12 +91,12 @@ program
|
|
|
157
91
|
const stats = (0, formatter_1.calculateScore)(findings);
|
|
158
92
|
const gitInfo = (0, formatter_1.getGitInfo)(scanDir);
|
|
159
93
|
let remediationData = undefined;
|
|
160
|
-
// Generate AI Remediation
|
|
161
|
-
if (options.fix || true) {
|
|
94
|
+
// Generate AI Remediation only if not in CI mode to save time, or if explicitly asked
|
|
95
|
+
if (findings.length > 0 && !options.ci && (options.fix || true)) {
|
|
162
96
|
try {
|
|
163
97
|
const explainer = new ai_engine_1.ContextualExplainer();
|
|
164
98
|
const primaryFinding = findings[0];
|
|
165
|
-
let snippet = primaryFinding.codeSnippet || '
|
|
99
|
+
let snippet = primaryFinding.codeSnippet || '';
|
|
166
100
|
if (primaryFinding.file && (0, fs_1.existsSync)((0, path_1.join)(scanDir, primaryFinding.file))) {
|
|
167
101
|
const content = (0, fs_1.readFileSync)((0, path_1.join)(scanDir, primaryFinding.file), 'utf-8');
|
|
168
102
|
const lines = content.split('\n');
|
|
@@ -170,44 +104,89 @@ program
|
|
|
170
104
|
snippet = lines.slice(Math.max(0, targetLine - 3), targetLine + 3).join('\n');
|
|
171
105
|
}
|
|
172
106
|
const explanation = await explainer.explainFinding(primaryFinding, { codeContext: snippet });
|
|
173
|
-
const diffSnippet = [
|
|
174
|
-
'10 # config/aws.py',
|
|
175
|
-
'- 11 AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"',
|
|
176
|
-
'+ 12 AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY")',
|
|
177
|
-
'13'
|
|
178
|
-
].join('\n');
|
|
179
107
|
remediationData = {
|
|
180
|
-
findingId: primaryFinding.id
|
|
181
|
-
issue: explanation.summary
|
|
182
|
-
impact:
|
|
183
|
-
recommendation: explanation.remediation
|
|
184
|
-
diffSnippet: explanation.codeFix
|
|
185
|
-
confidence:
|
|
108
|
+
findingId: primaryFinding.id,
|
|
109
|
+
issue: explanation.summary,
|
|
110
|
+
impact: explanation.details || 'Potential security impact based on context.',
|
|
111
|
+
recommendation: explanation.remediation,
|
|
112
|
+
diffSnippet: explanation.codeFix || '',
|
|
113
|
+
confidence: 90
|
|
186
114
|
};
|
|
187
115
|
}
|
|
188
116
|
catch (e) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
impact: 'This can lead to unauthorized access to your AWS resources.',
|
|
193
|
-
recommendation: 'Use environment variables or AWS Secrets Manager to store secrets.',
|
|
194
|
-
diffSnippet: [
|
|
195
|
-
'10 # config/aws.py',
|
|
196
|
-
'- 11 AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"',
|
|
197
|
-
'+ 12 AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY")',
|
|
198
|
-
'13'
|
|
199
|
-
].join('\n'),
|
|
200
|
-
confidence: 98
|
|
201
|
-
};
|
|
117
|
+
if (!options.ci) {
|
|
118
|
+
console.error(chalk_1.default.yellow('AI Remediation generation failed.'), e);
|
|
119
|
+
}
|
|
202
120
|
}
|
|
203
121
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
122
|
+
if (!options.ci) {
|
|
123
|
+
// Render the beautiful cyber dashboard interactively
|
|
124
|
+
(0, formatter_1.renderDashboard)({
|
|
125
|
+
findings,
|
|
126
|
+
stats,
|
|
127
|
+
gitInfo,
|
|
128
|
+
duration,
|
|
129
|
+
remediation: remediationData
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// CI Output
|
|
134
|
+
console.log(`VibeGuard CI Scan Complete. Duration: ${duration}`);
|
|
135
|
+
console.log(`Findings: ${findings.length}`);
|
|
136
|
+
console.log(`Risk Score: ${stats.score}/100 (${stats.riskLevel})`);
|
|
137
|
+
const critical = findings.filter(f => (f.severity || '').toUpperCase() === types_1.Severity.CRITICAL).length;
|
|
138
|
+
const high = findings.filter(f => (f.severity || '').toUpperCase() === types_1.Severity.HIGH).length;
|
|
139
|
+
console.log(`Critical: ${critical}, High: ${high}`);
|
|
140
|
+
}
|
|
141
|
+
// Sync with Web Dashboard
|
|
142
|
+
const syncSpinner = (0, ora_1.default)({
|
|
143
|
+
text: chalk_1.default.dim('Syncing results to VibeGuard Dashboard...'),
|
|
144
|
+
spinner: 'dots',
|
|
145
|
+
isSilent: options.ci
|
|
146
|
+
}).start();
|
|
147
|
+
try {
|
|
148
|
+
const API_URL = process.env.VIBEGUARD_API_URL || 'http://localhost:3001';
|
|
149
|
+
const API_KEY = process.env.VIBEGUARD_API_KEY;
|
|
150
|
+
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
'Authorization': `Bearer ${API_KEY}`
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
repositoryName: gitInfo.name || 'Local Project',
|
|
158
|
+
repositoryUrl: gitInfo.name || 'local',
|
|
159
|
+
numericScore: stats.score,
|
|
160
|
+
score: stats.riskLevel,
|
|
161
|
+
findings: findings
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
if (response.ok) {
|
|
165
|
+
syncSpinner.succeed(chalk_1.default.dim('Results synced to dashboard.'));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
syncSpinner.fail(chalk_1.default.dim('Failed to sync results to dashboard.'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
syncSpinner.warn(chalk_1.default.dim('Dashboard API unreachable. Skipping sync.'));
|
|
173
|
+
}
|
|
174
|
+
// Exit codes
|
|
175
|
+
if (hasSystemError) {
|
|
176
|
+
process.exit(2);
|
|
177
|
+
}
|
|
178
|
+
// Policy fail if we have CRITICAL or HIGH findings
|
|
179
|
+
const criticalOrHighCount = findings.filter(f => {
|
|
180
|
+
const s = (f.severity || '').toUpperCase();
|
|
181
|
+
return s === types_1.Severity.CRITICAL || s === types_1.Severity.HIGH;
|
|
182
|
+
}).length;
|
|
183
|
+
if (criticalOrHighCount > 0) {
|
|
184
|
+
if (options.ci)
|
|
185
|
+
console.error(chalk_1.default.red('Security Policy FAILED.'));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
if (options.ci)
|
|
189
|
+
console.log(chalk_1.default.green('Security Policy PASSED.'));
|
|
190
|
+
process.exit(0);
|
|
212
191
|
});
|
|
213
192
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -21,24 +21,58 @@ program
|
|
|
21
21
|
.description('Run a security scan on the current directory or target path')
|
|
22
22
|
.option('-d, --dir <path>', 'Directory to scan', process.cwd())
|
|
23
23
|
.option('--fix', 'Automatically generate AI remediation fixes and interactive diff')
|
|
24
|
-
.option('--
|
|
24
|
+
.option('--ci', 'Run in non-interactive CI mode and exit with policy status code')
|
|
25
25
|
.action(async (targetPath, options) => {
|
|
26
26
|
const scanDir = targetPath || options.dir || process.cwd();
|
|
27
27
|
const startTime = Date.now();
|
|
28
|
+
let hasSystemError = false;
|
|
28
29
|
|
|
30
|
+
// Use a minimal spinner if in CI mode, or bypass ora entirely if preferred.
|
|
31
|
+
// For simplicity, we just won't render the interactive dashboard if --ci is set.
|
|
29
32
|
const spinner = ora({
|
|
30
33
|
text: chalk.hex('#00E5FF')('Scanning repository for vulnerabilities (SAST, SCA, Secrets, IaC)...'),
|
|
31
|
-
spinner: 'dots'
|
|
34
|
+
spinner: 'dots',
|
|
35
|
+
isSilent: options.ci
|
|
32
36
|
}).start();
|
|
33
37
|
|
|
34
38
|
let findings: NormalizedFinding[] = [];
|
|
35
39
|
|
|
36
40
|
try {
|
|
37
|
-
// Initialize the security orchestrator and
|
|
41
|
+
// Initialize the security orchestrator and scanners
|
|
38
42
|
const { Orchestrator } = require('@maverick006/security-engine');
|
|
39
|
-
const
|
|
43
|
+
const scanners: any[] = [];
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
try {
|
|
46
|
+
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
47
|
+
scanners.push(new NpmAuditScanner());
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
52
|
+
scanners.push(new TrivyScanner());
|
|
53
|
+
} catch (e) {}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { SemgrepScanner } = require('@maverick006/scanner-semgrep');
|
|
57
|
+
scanners.push(new SemgrepScanner());
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const { GitleaksScanner } = require('@maverick006/scanner-gitleaks');
|
|
62
|
+
scanners.push(new GitleaksScanner());
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const { CheckovScanner } = require('@maverick006/scanner-checkov');
|
|
67
|
+
scanners.push(new CheckovScanner());
|
|
68
|
+
} catch (e) {}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const { ZapScanner } = require('@maverick006/scanner-zap');
|
|
72
|
+
scanners.push(new ZapScanner());
|
|
73
|
+
} catch (e) {}
|
|
74
|
+
|
|
75
|
+
const orchestrator = new Orchestrator(scanners);
|
|
42
76
|
|
|
43
77
|
const scanResult = await orchestrator.runScan({
|
|
44
78
|
scanId: `scan-${Date.now()}`,
|
|
@@ -49,109 +83,8 @@ program
|
|
|
49
83
|
|
|
50
84
|
findings = scanResult.findings || [];
|
|
51
85
|
} catch (err) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// If demo mode or no findings found in empty directory, provide rich showcase findings matching design
|
|
56
|
-
if (options.demo || findings.length === 0) {
|
|
57
|
-
findings = [
|
|
58
|
-
{
|
|
59
|
-
id: 'VG-CRIT-001',
|
|
60
|
-
title: 'Hardcoded AWS Secret Key',
|
|
61
|
-
severity: Severity.CRITICAL,
|
|
62
|
-
scanner: 'Gitleaks',
|
|
63
|
-
file: 'config/aws.py',
|
|
64
|
-
line: 12,
|
|
65
|
-
description: 'AWS Secret Key is hardcoded in the source code.',
|
|
66
|
-
codeSnippet: 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"'
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: 'VG-CRIT-002',
|
|
70
|
-
title: 'Exposed JWT Secret',
|
|
71
|
-
severity: Severity.CRITICAL,
|
|
72
|
-
scanner: 'Gitleaks',
|
|
73
|
-
file: 'config/auth.py',
|
|
74
|
-
line: 8,
|
|
75
|
-
description: 'Hardcoded JWT secret token detected.',
|
|
76
|
-
codeSnippet: 'const JWT_SECRET = "super_secret_123"'
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: 'VG-HIGH-003',
|
|
80
|
-
title: 'SQL Injection Risk',
|
|
81
|
-
severity: Severity.HIGH,
|
|
82
|
-
scanner: 'Semgrep',
|
|
83
|
-
file: 'api/users.py',
|
|
84
|
-
line: 45,
|
|
85
|
-
description: 'Direct concatenation of user input into SQL query.',
|
|
86
|
-
codeSnippet: 'query = "SELECT * FROM users WHERE name = " + req.query.name'
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: 'VG-HIGH-004',
|
|
90
|
-
title: 'Outdated Dependency (lodash)',
|
|
91
|
-
severity: Severity.HIGH,
|
|
92
|
-
scanner: 'npm-audit',
|
|
93
|
-
file: 'package.json',
|
|
94
|
-
line: 23,
|
|
95
|
-
description: 'Vulnerable prototype pollution in lodash version.',
|
|
96
|
-
codeSnippet: '"lodash": "4.17.15"'
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: 'VG-HIGH-005',
|
|
100
|
-
title: 'Unsafe Deserialization',
|
|
101
|
-
severity: Severity.HIGH,
|
|
102
|
-
scanner: 'Semgrep',
|
|
103
|
-
file: 'utils/parser.py',
|
|
104
|
-
line: 78,
|
|
105
|
-
description: 'Unsafe pickle.loads execution.',
|
|
106
|
-
codeSnippet: 'data = pickle.loads(user_input)'
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: 'VG-MED-006',
|
|
110
|
-
title: 'S3 Bucket Public Read',
|
|
111
|
-
severity: Severity.MEDIUM,
|
|
112
|
-
scanner: 'Checkov',
|
|
113
|
-
file: 'iac/s3.tf',
|
|
114
|
-
line: 14,
|
|
115
|
-
description: 'Public read access enabled on production storage bucket.',
|
|
116
|
-
codeSnippet: 'acl = "public-read"'
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: 'VG-MED-007',
|
|
120
|
-
title: 'Missing Rate Limiting',
|
|
121
|
-
severity: Severity.MEDIUM,
|
|
122
|
-
scanner: 'Semgrep',
|
|
123
|
-
file: 'api/auth.py',
|
|
124
|
-
line: 32,
|
|
125
|
-
description: 'Authentication endpoint lacks rate limiting protection.'
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
id: 'VG-MED-008',
|
|
129
|
-
title: 'CORS Misconfiguration',
|
|
130
|
-
severity: Severity.MEDIUM,
|
|
131
|
-
scanner: 'Semgrep',
|
|
132
|
-
file: 'web/middleware.py',
|
|
133
|
-
line: 19,
|
|
134
|
-
description: 'Wildcard CORS origin enabled in production middleware.'
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
id: 'VG-LOW-009',
|
|
138
|
-
title: 'Unused Dependency',
|
|
139
|
-
severity: Severity.LOW,
|
|
140
|
-
scanner: 'npm-audit',
|
|
141
|
-
file: 'package.json',
|
|
142
|
-
line: 102,
|
|
143
|
-
description: 'Unused package detected.'
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
id: 'VG-LOW-010',
|
|
147
|
-
title: 'Missing Security Headers',
|
|
148
|
-
severity: Severity.LOW,
|
|
149
|
-
scanner: 'Zap',
|
|
150
|
-
file: 'web/server.py',
|
|
151
|
-
line: 56,
|
|
152
|
-
description: 'Strict-Transport-Security header is not set.'
|
|
153
|
-
}
|
|
154
|
-
];
|
|
86
|
+
console.error(chalk.red('Orchestrator failed to run scans.'), err);
|
|
87
|
+
hasSystemError = true;
|
|
155
88
|
}
|
|
156
89
|
|
|
157
90
|
spinner.stop();
|
|
@@ -166,13 +99,13 @@ program
|
|
|
166
99
|
|
|
167
100
|
let remediationData: any = undefined;
|
|
168
101
|
|
|
169
|
-
// Generate AI Remediation
|
|
170
|
-
if (options.fix || true) {
|
|
102
|
+
// Generate AI Remediation only if not in CI mode to save time, or if explicitly asked
|
|
103
|
+
if (findings.length > 0 && !options.ci && (options.fix || true)) {
|
|
171
104
|
try {
|
|
172
105
|
const explainer = new ContextualExplainer();
|
|
173
106
|
const primaryFinding = findings[0];
|
|
174
107
|
|
|
175
|
-
let snippet = primaryFinding.codeSnippet || '
|
|
108
|
+
let snippet = primaryFinding.codeSnippet || '';
|
|
176
109
|
if (primaryFinding.file && existsSync(join(scanDir, primaryFinding.file))) {
|
|
177
110
|
const content = readFileSync(join(scanDir, primaryFinding.file), 'utf-8');
|
|
178
111
|
const lines = content.split('\n');
|
|
@@ -182,46 +115,92 @@ program
|
|
|
182
115
|
|
|
183
116
|
const explanation = await explainer.explainFinding(primaryFinding, { codeContext: snippet });
|
|
184
117
|
|
|
185
|
-
const diffSnippet = [
|
|
186
|
-
'10 # config/aws.py',
|
|
187
|
-
'- 11 AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"',
|
|
188
|
-
'+ 12 AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY")',
|
|
189
|
-
'13'
|
|
190
|
-
].join('\n');
|
|
191
|
-
|
|
192
118
|
remediationData = {
|
|
193
|
-
findingId: primaryFinding.id
|
|
194
|
-
issue: explanation.summary
|
|
195
|
-
impact:
|
|
196
|
-
recommendation: explanation.remediation
|
|
197
|
-
diffSnippet: explanation.codeFix
|
|
198
|
-
confidence:
|
|
119
|
+
findingId: primaryFinding.id,
|
|
120
|
+
issue: explanation.summary,
|
|
121
|
+
impact: explanation.details || 'Potential security impact based on context.',
|
|
122
|
+
recommendation: explanation.remediation,
|
|
123
|
+
diffSnippet: explanation.codeFix || '',
|
|
124
|
+
confidence: 90
|
|
199
125
|
};
|
|
200
126
|
} catch (e: any) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
impact: 'This can lead to unauthorized access to your AWS resources.',
|
|
205
|
-
recommendation: 'Use environment variables or AWS Secrets Manager to store secrets.',
|
|
206
|
-
diffSnippet: [
|
|
207
|
-
'10 # config/aws.py',
|
|
208
|
-
'- 11 AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"',
|
|
209
|
-
'+ 12 AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY")',
|
|
210
|
-
'13'
|
|
211
|
-
].join('\n'),
|
|
212
|
-
confidence: 98
|
|
213
|
-
};
|
|
127
|
+
if (!options.ci) {
|
|
128
|
+
console.error(chalk.yellow('AI Remediation generation failed.'), e);
|
|
129
|
+
}
|
|
214
130
|
}
|
|
215
131
|
}
|
|
216
132
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
133
|
+
if (!options.ci) {
|
|
134
|
+
// Render the beautiful cyber dashboard interactively
|
|
135
|
+
renderDashboard({
|
|
136
|
+
findings,
|
|
137
|
+
stats,
|
|
138
|
+
gitInfo,
|
|
139
|
+
duration,
|
|
140
|
+
remediation: remediationData
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
// CI Output
|
|
144
|
+
console.log(`VibeGuard CI Scan Complete. Duration: ${duration}`);
|
|
145
|
+
console.log(`Findings: ${findings.length}`);
|
|
146
|
+
console.log(`Risk Score: ${stats.score}/100 (${stats.riskLevel})`);
|
|
147
|
+
const critical = findings.filter(f => (f.severity || '').toUpperCase() === Severity.CRITICAL).length;
|
|
148
|
+
const high = findings.filter(f => (f.severity || '').toUpperCase() === Severity.HIGH).length;
|
|
149
|
+
console.log(`Critical: ${critical}, High: ${high}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Sync with Web Dashboard
|
|
153
|
+
const syncSpinner = ora({
|
|
154
|
+
text: chalk.dim('Syncing results to VibeGuard Dashboard...'),
|
|
155
|
+
spinner: 'dots',
|
|
156
|
+
isSilent: options.ci
|
|
157
|
+
}).start();
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const API_URL = process.env.VIBEGUARD_API_URL || 'http://localhost:3001';
|
|
161
|
+
const API_KEY = process.env.VIBEGUARD_API_KEY;
|
|
162
|
+
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
'Authorization': `Bearer ${API_KEY}`
|
|
167
|
+
},
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
repositoryName: gitInfo.name || 'Local Project',
|
|
170
|
+
repositoryUrl: gitInfo.name || 'local',
|
|
171
|
+
numericScore: stats.score,
|
|
172
|
+
score: stats.riskLevel,
|
|
173
|
+
findings: findings
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (response.ok) {
|
|
178
|
+
syncSpinner.succeed(chalk.dim('Results synced to dashboard.'));
|
|
179
|
+
} else {
|
|
180
|
+
syncSpinner.fail(chalk.dim('Failed to sync results to dashboard.'));
|
|
181
|
+
}
|
|
182
|
+
} catch (e) {
|
|
183
|
+
syncSpinner.warn(chalk.dim('Dashboard API unreachable. Skipping sync.'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Exit codes
|
|
187
|
+
if (hasSystemError) {
|
|
188
|
+
process.exit(2);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Policy fail if we have CRITICAL or HIGH findings
|
|
192
|
+
const criticalOrHighCount = findings.filter(f => {
|
|
193
|
+
const s = (f.severity || '').toUpperCase();
|
|
194
|
+
return s === Severity.CRITICAL || s === Severity.HIGH;
|
|
195
|
+
}).length;
|
|
196
|
+
|
|
197
|
+
if (criticalOrHighCount > 0) {
|
|
198
|
+
if (options.ci) console.error(chalk.red('Security Policy FAILED.'));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (options.ci) console.log(chalk.green('Security Policy PASSED.'));
|
|
203
|
+
process.exit(0);
|
|
225
204
|
});
|
|
226
205
|
|
|
227
206
|
program.parse(process.argv);
|