@maverick006/vibeguard 1.0.6 → 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/formatter.js CHANGED
@@ -18,6 +18,8 @@ function visibleWidth(str) {
18
18
  let width = 0;
19
19
  for (const char of plain) {
20
20
  const code = char.codePointAt(0) || 0;
21
+ if (code === 0xFE0F || code === 0xFE0E)
22
+ continue; // ignore variation selectors
21
23
  // Emojis and certain symbols take 2 terminal columns
22
24
  if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
23
25
  width += 2;
@@ -130,39 +132,32 @@ function renderDashboard(options) {
130
132
  const riskColor = stats.riskLevel === 'LOW RISK' ? green : stats.riskLevel === 'MEDIUM RISK' ? yellow : red;
131
133
  console.log('\n');
132
134
  // Print Top Row: Clock aligned right
133
- console.log(' '.repeat(58) + cyan.bold(timeStr));
134
- // Top Left Box lines (ASCII)
135
- const leftHeader = [
136
- `${cyan(shield[0])} ${cyan.bold(title[0])}`,
137
- `${cyan(shield[1])} ${cyan.bold(title[1])}`,
138
- `${cyan(shield[2])} ${cyan.bold(title[2])}`,
139
- `${cyan(shield[3])} ${cyan.bold(title[3])}`,
140
- `${cyan(shield[4])} ${cyan.bold(title[4])}`,
141
- `${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`,
142
- ` ${cyan('Scanning. Analyzing. Protecting.')}`
143
- ];
144
- // Top Right Risk Box lines
145
- const rightBox = [
146
- `${darkBorder('┌───────────────────────────────────────┐')}`,
147
- `${darkBorder('')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('')}`,
148
- `${darkBorder('')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('')}`,
149
- `${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('│')}`,
150
- `${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
151
- `${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
152
- `${darkBorder('└───────────────────────────────────────┘')}`
153
- ];
154
- for (let i = 0; i < leftHeader.length; i++) {
155
- const l = padVisible(leftHeader[i], 66);
156
- const r = rightBox[i] || '';
157
- console.log(`${l}${r}`);
158
- }
135
+ console.log(' '.repeat(65) + cyan.bold(timeStr));
136
+ // 1. Header (Shield + VIBEGUARD)
137
+ console.log(`${cyan(shield[0])} ${cyan.bold(title[0])}`);
138
+ console.log(`${cyan(shield[1])} ${cyan.bold(title[1])}`);
139
+ console.log(`${cyan(shield[2])} ${cyan.bold(title[2])}`);
140
+ console.log(`${cyan(shield[3])} ${cyan.bold(title[3])}`);
141
+ console.log(`${cyan(shield[4])} ${cyan.bold(title[4])}`);
142
+ console.log(`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`);
143
+ console.log(` ${cyan('Scanning. Analyzing. Protecting.')}\n`);
144
+ // 2. Risk Score Box (Placed cleanly BELOW VIBEGUARD Header)
145
+ const boxWidth = 74;
146
+ console.log(darkBorder(`┌${'─'.repeat(boxWidth)}┐`));
147
+ console.log(`${darkBorder('│')} ${cyan.bold('OVERALL RISK SCORE')}${' '.repeat(boxWidth - 20)}${darkBorder('│')}`);
148
+ console.log(`${darkBorder('')}${' '.repeat(boxWidth)}${darkBorder('│')}`);
149
+ const scoreLine1 = ` ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${red('CRITICAL')} ${String(stats.critical).padEnd(2, ' ')} ${orange('HIGH')} ${String(stats.high).padEnd(2, ' ')}`;
150
+ const scoreLine2 = ` ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${yellow('MEDIUM')} ${String(stats.medium).padEnd(2, ' ')} ${cyan('LOW')} ${String(stats.low).padEnd(2, ' ')}`;
151
+ console.log(`${darkBorder('│')}${padVisible(scoreLine1, boxWidth)}${darkBorder('│')}`);
152
+ console.log(`${darkBorder('│')}${padVisible(scoreLine2, boxWidth)}${darkBorder('│')}`);
153
+ console.log(darkBorder(`└${''.repeat(boxWidth)}┘`));
159
154
  console.log('');
160
- // 3-Column / Multi-panel Grid
155
+ // 3. Multi-panel Grid (Pipeline & Findings)
161
156
  const pipelineRows = [
162
- `${gray('</>')} SAST (Semgrep) ${green('✓ OK')}`,
157
+ `</> SAST (Semgrep) ${green('✓ OK')}`,
163
158
  `📦 Dependency Check (Trivy) ${green('✓ OK')}`,
164
159
  `🔑 Secrets Scan (Gitleaks) ${green('✓ OK')}`,
165
- `☁️ IaC Scan (Checkov) ${green('✓ OK')}`,
160
+ `🔒 IaC Scan (Checkov) ${green('✓ OK')}`,
166
161
  `🐳 Container Scan (Trivy) ${green('✓ OK')}`,
167
162
  `📑 Code Quality (ESLint) ${green('✓ OK')}`,
168
163
  ``,
@@ -175,7 +170,7 @@ function renderDashboard(options) {
175
170
  ];
176
171
  // Table of findings (top 10)
177
172
  const topFindings = findings.slice(0, 10);
178
- const tableHeader = `${dimGray(padVisible('ID', 12))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
173
+ const tableHeader = `${dimGray(padVisible('ID', 13))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
179
174
  const findingRows = [tableHeader];
180
175
  if (topFindings.length === 0) {
181
176
  findingRows.push(green(' ✓ No security vulnerabilities detected. Codebase is clean!'));
@@ -192,24 +187,24 @@ function renderDashboard(options) {
192
187
  else if (sev === 'MEDIUM')
193
188
  sevFormatted = yellow('MEDIUM ');
194
189
  const rawTitle = f.title || 'Security Finding';
195
- const titleTruncated = rawTitle.length > 25 ? rawTitle.slice(0, 23) + '..' : rawTitle;
190
+ const titleTruncated = rawTitle.length > 26 ? rawTitle.slice(0, 24) + '..' : rawTitle;
196
191
  const titleFormatted = padVisible(white(titleTruncated), 28);
197
192
  const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
198
- const idFormatted = padVisible(cyan(id), 12);
193
+ const idFormatted = padVisible(cyan(id), 13);
199
194
  findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
200
195
  }
201
196
  }
202
197
  // Print Section Headers with ANSI-safe padding
203
198
  const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
204
199
  const headerCol2 = cyan('▶ TOP FINDINGS');
205
- console.log(`\n${headerCol1} ${headerCol2}`);
200
+ console.log(`${headerCol1} ${headerCol2}`);
206
201
  const maxRows = Math.max(pipelineRows.length, findingRows.length);
207
202
  for (let i = 0; i < maxRows; i++) {
208
203
  const left = padVisible(pipelineRows[i] || '', 36);
209
204
  const right = findingRows[i] || '';
210
205
  console.log(`${left} ${right}`);
211
206
  }
212
- // AI Remediation Section
207
+ // 4. AI Remediation Section
213
208
  if (remediation) {
214
209
  console.log(`\n${cyan('▶ AI REMEDIATION (VG-AI)')}\n`);
215
210
  console.log(`${cyan('Finding:')} ${red.bold(remediation.findingId)}\n`);
@@ -235,7 +230,7 @@ function renderDashboard(options) {
235
230
  console.log(darkBorder('└────────────────────────────────────────────────────────────┘'));
236
231
  console.log(`\n${cyan('Confidence:')} ${green.bold(remediation.confidence + '%')}`);
237
232
  }
238
- // Summary Bar (bottom capsule)
233
+ // 5. Summary Bar (bottom capsule)
239
234
  console.log(`\n${cyan('▶ SUMMARY')}`);
240
235
  const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
241
236
  const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
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.6",
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/formatter.ts CHANGED
@@ -22,6 +22,7 @@ function visibleWidth(str: string): number {
22
22
  let width = 0;
23
23
  for (const char of plain) {
24
24
  const code = char.codePointAt(0) || 0;
25
+ if (code === 0xFE0F || code === 0xFE0E) continue; // ignore variation selectors
25
26
  // Emojis and certain symbols take 2 terminal columns
26
27
  if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
27
28
  width += 2;
@@ -152,43 +153,37 @@ export function renderDashboard(options: {
152
153
  console.log('\n');
153
154
 
154
155
  // Print Top Row: Clock aligned right
155
- console.log(' '.repeat(58) + cyan.bold(timeStr));
156
-
157
- // Top Left Box lines (ASCII)
158
- const leftHeader = [
159
- `${cyan(shield[0])} ${cyan.bold(title[0])}`,
160
- `${cyan(shield[1])} ${cyan.bold(title[1])}`,
161
- `${cyan(shield[2])} ${cyan.bold(title[2])}`,
162
- `${cyan(shield[3])} ${cyan.bold(title[3])}`,
163
- `${cyan(shield[4])} ${cyan.bold(title[4])}`,
164
- `${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`,
165
- ` ${cyan('Scanning. Analyzing. Protecting.')}`
166
- ];
167
-
168
- // Top Right Risk Box lines
169
- const rightBox = [
170
- `${darkBorder('┌───────────────────────────────────────┐')}`,
171
- `${darkBorder('│')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('│')}`,
172
- `${darkBorder('')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('')}`,
173
- `${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('')}`,
174
- `${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
175
- `${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
176
- `${darkBorder('└───────────────────────────────────────┘')}`
177
- ];
156
+ console.log(' '.repeat(65) + cyan.bold(timeStr));
157
+
158
+ // 1. Header (Shield + VIBEGUARD)
159
+ console.log(`${cyan(shield[0])} ${cyan.bold(title[0])}`);
160
+ console.log(`${cyan(shield[1])} ${cyan.bold(title[1])}`);
161
+ console.log(`${cyan(shield[2])} ${cyan.bold(title[2])}`);
162
+ console.log(`${cyan(shield[3])} ${cyan.bold(title[3])}`);
163
+ console.log(`${cyan(shield[4])} ${cyan.bold(title[4])}`);
164
+ console.log(`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`);
165
+ console.log(` ${cyan('Scanning. Analyzing. Protecting.')}\n`);
166
+
167
+ // 2. Risk Score Box (Placed cleanly BELOW VIBEGUARD Header)
168
+ const boxWidth = 74;
169
+ console.log(darkBorder(`┌${'─'.repeat(boxWidth)}┐`));
170
+ console.log(`${darkBorder('│')} ${cyan.bold('OVERALL RISK SCORE')}${' '.repeat(boxWidth - 20)}${darkBorder('│')}`);
171
+ console.log(`${darkBorder('')}${' '.repeat(boxWidth)}${darkBorder('│')}`);
172
+
173
+ const scoreLine1 = ` ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${red('CRITICAL')} ${String(stats.critical).padEnd(2, ' ')} ${orange('HIGH')} ${String(stats.high).padEnd(2, ' ')}`;
174
+ const scoreLine2 = ` ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${yellow('MEDIUM')} ${String(stats.medium).padEnd(2, ' ')} ${cyan('LOW')} ${String(stats.low).padEnd(2, ' ')}`;
178
175
 
179
- for (let i = 0; i < leftHeader.length; i++) {
180
- const l = padVisible(leftHeader[i], 66);
181
- const r = rightBox[i] || '';
182
- console.log(`${l}${r}`);
183
- }
176
+ console.log(`${darkBorder('│')}${padVisible(scoreLine1, boxWidth)}${darkBorder('│')}`);
177
+ console.log(`${darkBorder('│')}${padVisible(scoreLine2, boxWidth)}${darkBorder('│')}`);
178
+ console.log(darkBorder(`└${''.repeat(boxWidth)}┘`));
184
179
  console.log('');
185
180
 
186
- // 3-Column / Multi-panel Grid
181
+ // 3. Multi-panel Grid (Pipeline & Findings)
187
182
  const pipelineRows = [
188
- `${gray('</>')} SAST (Semgrep) ${green('✓ OK')}`,
183
+ `</> SAST (Semgrep) ${green('✓ OK')}`,
189
184
  `📦 Dependency Check (Trivy) ${green('✓ OK')}`,
190
185
  `🔑 Secrets Scan (Gitleaks) ${green('✓ OK')}`,
191
- `☁️ IaC Scan (Checkov) ${green('✓ OK')}`,
186
+ `🔒 IaC Scan (Checkov) ${green('✓ OK')}`,
192
187
  `🐳 Container Scan (Trivy) ${green('✓ OK')}`,
193
188
  `📑 Code Quality (ESLint) ${green('✓ OK')}`,
194
189
  ``,
@@ -202,7 +197,7 @@ export function renderDashboard(options: {
202
197
 
203
198
  // Table of findings (top 10)
204
199
  const topFindings = findings.slice(0, 10);
205
- const tableHeader = `${dimGray(padVisible('ID', 12))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
200
+ const tableHeader = `${dimGray(padVisible('ID', 13))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
206
201
 
207
202
  const findingRows: string[] = [tableHeader];
208
203
 
@@ -218,11 +213,11 @@ export function renderDashboard(options: {
218
213
  else if (sev === 'MEDIUM') sevFormatted = yellow('MEDIUM ');
219
214
 
220
215
  const rawTitle = f.title || 'Security Finding';
221
- const titleTruncated = rawTitle.length > 25 ? rawTitle.slice(0, 23) + '..' : rawTitle;
216
+ const titleTruncated = rawTitle.length > 26 ? rawTitle.slice(0, 24) + '..' : rawTitle;
222
217
  const titleFormatted = padVisible(white(titleTruncated), 28);
223
218
 
224
219
  const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
225
- const idFormatted = padVisible(cyan(id), 12);
220
+ const idFormatted = padVisible(cyan(id), 13);
226
221
  findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
227
222
  }
228
223
  }
@@ -230,7 +225,7 @@ export function renderDashboard(options: {
230
225
  // Print Section Headers with ANSI-safe padding
231
226
  const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
232
227
  const headerCol2 = cyan('▶ TOP FINDINGS');
233
- console.log(`\n${headerCol1} ${headerCol2}`);
228
+ console.log(`${headerCol1} ${headerCol2}`);
234
229
 
235
230
  const maxRows = Math.max(pipelineRows.length, findingRows.length);
236
231
  for (let i = 0; i < maxRows; i++) {
@@ -239,7 +234,7 @@ export function renderDashboard(options: {
239
234
  console.log(`${left} ${right}`);
240
235
  }
241
236
 
242
- // AI Remediation Section
237
+ // 4. AI Remediation Section
243
238
  if (remediation) {
244
239
  console.log(`\n${cyan('▶ AI REMEDIATION (VG-AI)')}\n`);
245
240
  console.log(`${cyan('Finding:')} ${red.bold(remediation.findingId)}\n`);
@@ -265,7 +260,7 @@ export function renderDashboard(options: {
265
260
  console.log(`\n${cyan('Confidence:')} ${green.bold(remediation.confidence + '%')}`);
266
261
  }
267
262
 
268
- // Summary Bar (bottom capsule)
263
+ // 5. Summary Bar (bottom capsule)
269
264
  console.log(`\n${cyan('▶ SUMMARY')}`);
270
265
  const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
271
266
  const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
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);