@maverick006/vibeguard 1.0.7 → 1.0.8
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 +103 -142
- package/package.json +6 -1
- package/src/index.ts +106 -145
package/dist/index.js
CHANGED
|
@@ -23,20 +23,36 @@ 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
42
|
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
39
|
-
const
|
|
43
|
+
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
44
|
+
const { SemgrepScanner } = require('@maverick006/scanner-semgrep');
|
|
45
|
+
const { GitleaksScanner } = require('@maverick006/scanner-gitleaks');
|
|
46
|
+
const { CheckovScanner } = require('@maverick006/scanner-checkov');
|
|
47
|
+
const { ZapScanner } = require('@maverick006/scanner-zap');
|
|
48
|
+
const orchestrator = new Orchestrator([
|
|
49
|
+
new NpmAuditScanner(),
|
|
50
|
+
new TrivyScanner(),
|
|
51
|
+
new SemgrepScanner(),
|
|
52
|
+
new GitleaksScanner(),
|
|
53
|
+
new CheckovScanner(),
|
|
54
|
+
new ZapScanner()
|
|
55
|
+
]);
|
|
40
56
|
const scanResult = await orchestrator.runScan({
|
|
41
57
|
scanId: `scan-${Date.now()}`,
|
|
42
58
|
repositoryUrl: 'local',
|
|
@@ -46,108 +62,8 @@ program
|
|
|
46
62
|
findings = scanResult.findings || [];
|
|
47
63
|
}
|
|
48
64
|
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
|
-
];
|
|
65
|
+
console.error(chalk_1.default.red('Orchestrator failed to run scans.'), err);
|
|
66
|
+
hasSystemError = true;
|
|
151
67
|
}
|
|
152
68
|
spinner.stop();
|
|
153
69
|
const elapsedMs = Date.now() - startTime;
|
|
@@ -157,12 +73,12 @@ program
|
|
|
157
73
|
const stats = (0, formatter_1.calculateScore)(findings);
|
|
158
74
|
const gitInfo = (0, formatter_1.getGitInfo)(scanDir);
|
|
159
75
|
let remediationData = undefined;
|
|
160
|
-
// Generate AI Remediation
|
|
161
|
-
if (options.fix || true) {
|
|
76
|
+
// Generate AI Remediation only if not in CI mode to save time, or if explicitly asked
|
|
77
|
+
if (findings.length > 0 && !options.ci && (options.fix || true)) {
|
|
162
78
|
try {
|
|
163
79
|
const explainer = new ai_engine_1.ContextualExplainer();
|
|
164
80
|
const primaryFinding = findings[0];
|
|
165
|
-
let snippet = primaryFinding.codeSnippet || '
|
|
81
|
+
let snippet = primaryFinding.codeSnippet || '';
|
|
166
82
|
if (primaryFinding.file && (0, fs_1.existsSync)((0, path_1.join)(scanDir, primaryFinding.file))) {
|
|
167
83
|
const content = (0, fs_1.readFileSync)((0, path_1.join)(scanDir, primaryFinding.file), 'utf-8');
|
|
168
84
|
const lines = content.split('\n');
|
|
@@ -170,44 +86,89 @@ program
|
|
|
170
86
|
snippet = lines.slice(Math.max(0, targetLine - 3), targetLine + 3).join('\n');
|
|
171
87
|
}
|
|
172
88
|
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
89
|
remediationData = {
|
|
180
|
-
findingId: primaryFinding.id
|
|
181
|
-
issue: explanation.summary
|
|
182
|
-
impact:
|
|
183
|
-
recommendation: explanation.remediation
|
|
184
|
-
diffSnippet: explanation.codeFix
|
|
185
|
-
confidence:
|
|
90
|
+
findingId: primaryFinding.id,
|
|
91
|
+
issue: explanation.summary,
|
|
92
|
+
impact: explanation.details || 'Potential security impact based on context.',
|
|
93
|
+
recommendation: explanation.remediation,
|
|
94
|
+
diffSnippet: explanation.codeFix || '',
|
|
95
|
+
confidence: 90
|
|
186
96
|
};
|
|
187
97
|
}
|
|
188
98
|
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
|
-
};
|
|
99
|
+
if (!options.ci) {
|
|
100
|
+
console.error(chalk_1.default.yellow('AI Remediation generation failed.'), e);
|
|
101
|
+
}
|
|
202
102
|
}
|
|
203
103
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
104
|
+
if (!options.ci) {
|
|
105
|
+
// Render the beautiful cyber dashboard interactively
|
|
106
|
+
(0, formatter_1.renderDashboard)({
|
|
107
|
+
findings,
|
|
108
|
+
stats,
|
|
109
|
+
gitInfo,
|
|
110
|
+
duration,
|
|
111
|
+
remediation: remediationData
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// CI Output
|
|
116
|
+
console.log(`VibeGuard CI Scan Complete. Duration: ${duration}`);
|
|
117
|
+
console.log(`Findings: ${findings.length}`);
|
|
118
|
+
console.log(`Risk Score: ${stats.score}/100 (${stats.riskLevel})`);
|
|
119
|
+
const critical = findings.filter(f => (f.severity || '').toUpperCase() === types_1.Severity.CRITICAL).length;
|
|
120
|
+
const high = findings.filter(f => (f.severity || '').toUpperCase() === types_1.Severity.HIGH).length;
|
|
121
|
+
console.log(`Critical: ${critical}, High: ${high}`);
|
|
122
|
+
}
|
|
123
|
+
// Sync with Web Dashboard
|
|
124
|
+
const syncSpinner = (0, ora_1.default)({
|
|
125
|
+
text: chalk_1.default.dim('Syncing results to VibeGuard Dashboard...'),
|
|
126
|
+
spinner: 'dots',
|
|
127
|
+
isSilent: options.ci
|
|
128
|
+
}).start();
|
|
129
|
+
try {
|
|
130
|
+
const API_URL = process.env.VIBEGUARD_API_URL || 'http://localhost:3001';
|
|
131
|
+
const API_KEY = process.env.VIBEGUARD_API_KEY;
|
|
132
|
+
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'Authorization': `Bearer ${API_KEY}`
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
repositoryName: gitInfo.name || 'Local Project',
|
|
140
|
+
repositoryUrl: gitInfo.name || 'local',
|
|
141
|
+
numericScore: stats.score,
|
|
142
|
+
score: stats.riskLevel,
|
|
143
|
+
findings: findings
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
if (response.ok) {
|
|
147
|
+
syncSpinner.succeed(chalk_1.default.dim('Results synced to dashboard.'));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
syncSpinner.fail(chalk_1.default.dim('Failed to sync results to dashboard.'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
syncSpinner.warn(chalk_1.default.dim('Dashboard API unreachable. Skipping sync.'));
|
|
155
|
+
}
|
|
156
|
+
// Exit codes
|
|
157
|
+
if (hasSystemError) {
|
|
158
|
+
process.exit(2);
|
|
159
|
+
}
|
|
160
|
+
// Policy fail if we have CRITICAL or HIGH findings
|
|
161
|
+
const criticalOrHighCount = findings.filter(f => {
|
|
162
|
+
const s = (f.severity || '').toUpperCase();
|
|
163
|
+
return s === types_1.Severity.CRITICAL || s === types_1.Severity.HIGH;
|
|
164
|
+
}).length;
|
|
165
|
+
if (criticalOrHighCount > 0) {
|
|
166
|
+
if (options.ci)
|
|
167
|
+
console.error(chalk_1.default.red('Security Policy FAILED.'));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
if (options.ci)
|
|
171
|
+
console.log(chalk_1.default.green('Security Policy PASSED.'));
|
|
172
|
+
process.exit(0);
|
|
212
173
|
});
|
|
213
174
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maverick006/vibeguard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "VibeGuard CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"@maverick006/ai-engine": "*",
|
|
16
16
|
"@maverick006/security-engine": "*",
|
|
17
17
|
"@maverick006/scanner-npm-audit": "*",
|
|
18
|
+
"@maverick006/scanner-trivy": "*",
|
|
19
|
+
"@maverick006/scanner-semgrep": "*",
|
|
20
|
+
"@maverick006/scanner-gitleaks": "*",
|
|
21
|
+
"@maverick006/scanner-checkov": "*",
|
|
22
|
+
"@maverick006/scanner-zap": "*",
|
|
18
23
|
"@maverick006/types": "*",
|
|
19
24
|
"chalk": "^4.1.2",
|
|
20
25
|
"commander": "^11.1.0",
|
package/src/index.ts
CHANGED
|
@@ -21,24 +21,40 @@ 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
43
|
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
40
|
-
|
|
41
|
-
const
|
|
44
|
+
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
45
|
+
const { SemgrepScanner } = require('@maverick006/scanner-semgrep');
|
|
46
|
+
const { GitleaksScanner } = require('@maverick006/scanner-gitleaks');
|
|
47
|
+
const { CheckovScanner } = require('@maverick006/scanner-checkov');
|
|
48
|
+
const { ZapScanner } = require('@maverick006/scanner-zap');
|
|
49
|
+
|
|
50
|
+
const orchestrator = new Orchestrator([
|
|
51
|
+
new NpmAuditScanner(),
|
|
52
|
+
new TrivyScanner(),
|
|
53
|
+
new SemgrepScanner(),
|
|
54
|
+
new GitleaksScanner(),
|
|
55
|
+
new CheckovScanner(),
|
|
56
|
+
new ZapScanner()
|
|
57
|
+
]);
|
|
42
58
|
|
|
43
59
|
const scanResult = await orchestrator.runScan({
|
|
44
60
|
scanId: `scan-${Date.now()}`,
|
|
@@ -49,109 +65,8 @@ program
|
|
|
49
65
|
|
|
50
66
|
findings = scanResult.findings || [];
|
|
51
67
|
} 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
|
-
];
|
|
68
|
+
console.error(chalk.red('Orchestrator failed to run scans.'), err);
|
|
69
|
+
hasSystemError = true;
|
|
155
70
|
}
|
|
156
71
|
|
|
157
72
|
spinner.stop();
|
|
@@ -166,13 +81,13 @@ program
|
|
|
166
81
|
|
|
167
82
|
let remediationData: any = undefined;
|
|
168
83
|
|
|
169
|
-
// Generate AI Remediation
|
|
170
|
-
if (options.fix || true) {
|
|
84
|
+
// Generate AI Remediation only if not in CI mode to save time, or if explicitly asked
|
|
85
|
+
if (findings.length > 0 && !options.ci && (options.fix || true)) {
|
|
171
86
|
try {
|
|
172
87
|
const explainer = new ContextualExplainer();
|
|
173
88
|
const primaryFinding = findings[0];
|
|
174
89
|
|
|
175
|
-
let snippet = primaryFinding.codeSnippet || '
|
|
90
|
+
let snippet = primaryFinding.codeSnippet || '';
|
|
176
91
|
if (primaryFinding.file && existsSync(join(scanDir, primaryFinding.file))) {
|
|
177
92
|
const content = readFileSync(join(scanDir, primaryFinding.file), 'utf-8');
|
|
178
93
|
const lines = content.split('\n');
|
|
@@ -182,46 +97,92 @@ program
|
|
|
182
97
|
|
|
183
98
|
const explanation = await explainer.explainFinding(primaryFinding, { codeContext: snippet });
|
|
184
99
|
|
|
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
100
|
remediationData = {
|
|
193
|
-
findingId: primaryFinding.id
|
|
194
|
-
issue: explanation.summary
|
|
195
|
-
impact:
|
|
196
|
-
recommendation: explanation.remediation
|
|
197
|
-
diffSnippet: explanation.codeFix
|
|
198
|
-
confidence:
|
|
101
|
+
findingId: primaryFinding.id,
|
|
102
|
+
issue: explanation.summary,
|
|
103
|
+
impact: explanation.details || 'Potential security impact based on context.',
|
|
104
|
+
recommendation: explanation.remediation,
|
|
105
|
+
diffSnippet: explanation.codeFix || '',
|
|
106
|
+
confidence: 90
|
|
199
107
|
};
|
|
200
108
|
} 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
|
-
};
|
|
109
|
+
if (!options.ci) {
|
|
110
|
+
console.error(chalk.yellow('AI Remediation generation failed.'), e);
|
|
111
|
+
}
|
|
214
112
|
}
|
|
215
113
|
}
|
|
216
114
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
115
|
+
if (!options.ci) {
|
|
116
|
+
// Render the beautiful cyber dashboard interactively
|
|
117
|
+
renderDashboard({
|
|
118
|
+
findings,
|
|
119
|
+
stats,
|
|
120
|
+
gitInfo,
|
|
121
|
+
duration,
|
|
122
|
+
remediation: remediationData
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
// CI Output
|
|
126
|
+
console.log(`VibeGuard CI Scan Complete. Duration: ${duration}`);
|
|
127
|
+
console.log(`Findings: ${findings.length}`);
|
|
128
|
+
console.log(`Risk Score: ${stats.score}/100 (${stats.riskLevel})`);
|
|
129
|
+
const critical = findings.filter(f => (f.severity || '').toUpperCase() === Severity.CRITICAL).length;
|
|
130
|
+
const high = findings.filter(f => (f.severity || '').toUpperCase() === Severity.HIGH).length;
|
|
131
|
+
console.log(`Critical: ${critical}, High: ${high}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Sync with Web Dashboard
|
|
135
|
+
const syncSpinner = ora({
|
|
136
|
+
text: chalk.dim('Syncing results to VibeGuard Dashboard...'),
|
|
137
|
+
spinner: 'dots',
|
|
138
|
+
isSilent: options.ci
|
|
139
|
+
}).start();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const API_URL = process.env.VIBEGUARD_API_URL || 'http://localhost:3001';
|
|
143
|
+
const API_KEY = process.env.VIBEGUARD_API_KEY;
|
|
144
|
+
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
'Authorization': `Bearer ${API_KEY}`
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify({
|
|
151
|
+
repositoryName: gitInfo.name || 'Local Project',
|
|
152
|
+
repositoryUrl: gitInfo.name || 'local',
|
|
153
|
+
numericScore: stats.score,
|
|
154
|
+
score: stats.riskLevel,
|
|
155
|
+
findings: findings
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (response.ok) {
|
|
160
|
+
syncSpinner.succeed(chalk.dim('Results synced to dashboard.'));
|
|
161
|
+
} else {
|
|
162
|
+
syncSpinner.fail(chalk.dim('Failed to sync results to dashboard.'));
|
|
163
|
+
}
|
|
164
|
+
} catch (e) {
|
|
165
|
+
syncSpinner.warn(chalk.dim('Dashboard API unreachable. Skipping sync.'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Exit codes
|
|
169
|
+
if (hasSystemError) {
|
|
170
|
+
process.exit(2);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Policy fail if we have CRITICAL or HIGH findings
|
|
174
|
+
const criticalOrHighCount = findings.filter(f => {
|
|
175
|
+
const s = (f.severity || '').toUpperCase();
|
|
176
|
+
return s === Severity.CRITICAL || s === Severity.HIGH;
|
|
177
|
+
}).length;
|
|
178
|
+
|
|
179
|
+
if (criticalOrHighCount > 0) {
|
|
180
|
+
if (options.ci) console.error(chalk.red('Security Policy FAILED.'));
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (options.ci) console.log(chalk.green('Security Policy PASSED.'));
|
|
185
|
+
process.exit(0);
|
|
225
186
|
});
|
|
226
187
|
|
|
227
188
|
program.parse(process.argv);
|