@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.
Files changed (3) hide show
  1. package/dist/index.js +103 -142
  2. package/package.json +6 -1
  3. 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('--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
42
  const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
39
- const orchestrator = new Orchestrator([new NpmAuditScanner()]);
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
- // 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
- ];
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 || 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"';
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 || '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
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
- 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
- };
99
+ if (!options.ci) {
100
+ console.error(chalk_1.default.yellow('AI Remediation generation failed.'), e);
101
+ }
202
102
  }
203
103
  }
204
- // Render the beautiful cyber dashboard
205
- (0, formatter_1.renderDashboard)({
206
- findings,
207
- stats,
208
- gitInfo,
209
- duration,
210
- remediation: remediationData
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.7",
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('--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
43
  const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
40
-
41
- const orchestrator = new Orchestrator([new NpmAuditScanner()]);
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
- // 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
- ];
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 || 'AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE"';
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 || '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
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
- 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
- };
109
+ if (!options.ci) {
110
+ console.error(chalk.yellow('AI Remediation generation failed.'), e);
111
+ }
214
112
  }
215
113
  }
216
114
 
217
- // Render the beautiful cyber dashboard
218
- renderDashboard({
219
- findings,
220
- stats,
221
- gitInfo,
222
- duration,
223
- remediation: remediationData
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);