@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.
Files changed (3) hide show
  1. package/dist/index.js +122 -143
  2. package/package.json +1 -1
  3. 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('--demo', 'Showcase the complete security dashboard with sample findings')
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 npm audit scanner
40
+ // Initialize the security orchestrator and scanners
37
41
  const { Orchestrator } = require('@maverick006/security-engine');
38
- const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
39
- const orchestrator = new Orchestrator([new NpmAuditScanner()]);
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
- // Fallback gracefully if scanner encounters environmental differences
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 || 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"';
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 || 'VG-CRIT-001',
181
- issue: explanation.summary || 'AWS Secret Key is hardcoded in the source code.',
182
- impact: 'This can lead to unauthorized access to your AWS resources and cloud infrastructure.',
183
- recommendation: explanation.remediation || 'Use environment variables or AWS Secrets Manager to store secrets.',
184
- diffSnippet: explanation.codeFix ? explanation.codeFix : diffSnippet,
185
- confidence: 98
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
- remediationData = {
190
- findingId: 'VG-CRIT-001',
191
- issue: 'AWS Secret Key is hardcoded in the source code.',
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
- // Render the beautiful cyber dashboard
205
- (0, formatter_1.renderDashboard)({
206
- findings,
207
- stats,
208
- gitInfo,
209
- duration,
210
- remediation: remediationData
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maverick006/vibeguard",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "VibeGuard CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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('--demo', 'Showcase the complete security dashboard with sample findings')
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 npm audit scanner
41
+ // Initialize the security orchestrator and scanners
38
42
  const { Orchestrator } = require('@maverick006/security-engine');
39
- const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
43
+ const scanners: any[] = [];
40
44
 
41
- const orchestrator = new Orchestrator([new NpmAuditScanner()]);
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
- // Fallback gracefully if scanner encounters environmental differences
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 || 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"';
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 || 'VG-CRIT-001',
194
- issue: explanation.summary || 'AWS Secret Key is hardcoded in the source code.',
195
- impact: 'This can lead to unauthorized access to your AWS resources and cloud infrastructure.',
196
- recommendation: explanation.remediation || 'Use environment variables or AWS Secrets Manager to store secrets.',
197
- diffSnippet: explanation.codeFix ? explanation.codeFix : diffSnippet,
198
- confidence: 98
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
- remediationData = {
202
- findingId: 'VG-CRIT-001',
203
- issue: 'AWS Secret Key is hardcoded in the source code.',
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
- // Render the beautiful cyber dashboard
218
- renderDashboard({
219
- findings,
220
- stats,
221
- gitInfo,
222
- duration,
223
- remediation: remediationData
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);