@rahul-sch/vibeguard 1.0.0

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 (93) hide show
  1. package/README.md +162 -0
  2. package/bin/vibeguard.js +2 -0
  3. package/dist/ai/cache.d.ts +5 -0
  4. package/dist/ai/cache.js +20 -0
  5. package/dist/ai/index.d.ts +9 -0
  6. package/dist/ai/index.js +71 -0
  7. package/dist/ai/prompts.d.ts +7 -0
  8. package/dist/ai/prompts.js +65 -0
  9. package/dist/ai/provider.d.ts +12 -0
  10. package/dist/ai/provider.js +93 -0
  11. package/dist/ai/types.d.ts +21 -0
  12. package/dist/ai/types.js +1 -0
  13. package/dist/cli/commands/fix.d.ts +7 -0
  14. package/dist/cli/commands/fix.js +140 -0
  15. package/dist/cli/commands/github.d.ts +6 -0
  16. package/dist/cli/commands/github.js +24 -0
  17. package/dist/cli/commands/scan.d.ts +5 -0
  18. package/dist/cli/commands/scan.js +54 -0
  19. package/dist/cli/index.d.ts +1 -0
  20. package/dist/cli/index.js +49 -0
  21. package/dist/cli/options.d.ts +17 -0
  22. package/dist/cli/options.js +27 -0
  23. package/dist/config/defaults.d.ts +17 -0
  24. package/dist/config/defaults.js +21 -0
  25. package/dist/config/index.d.ts +17 -0
  26. package/dist/config/index.js +119 -0
  27. package/dist/config/schema.d.ts +20 -0
  28. package/dist/config/schema.js +39 -0
  29. package/dist/engine/file-walker.d.ts +12 -0
  30. package/dist/engine/file-walker.js +61 -0
  31. package/dist/engine/filter.d.ts +3 -0
  32. package/dist/engine/filter.js +50 -0
  33. package/dist/engine/index.d.ts +10 -0
  34. package/dist/engine/index.js +54 -0
  35. package/dist/engine/matcher.d.ts +10 -0
  36. package/dist/engine/matcher.js +47 -0
  37. package/dist/fix/engine.d.ts +37 -0
  38. package/dist/fix/engine.js +121 -0
  39. package/dist/fix/index.d.ts +2 -0
  40. package/dist/fix/index.js +2 -0
  41. package/dist/fix/patch.d.ts +23 -0
  42. package/dist/fix/patch.js +94 -0
  43. package/dist/fix/strategies.d.ts +21 -0
  44. package/dist/fix/strategies.js +213 -0
  45. package/dist/fix/types.d.ts +48 -0
  46. package/dist/fix/types.js +1 -0
  47. package/dist/github/client.d.ts +10 -0
  48. package/dist/github/client.js +43 -0
  49. package/dist/github/comment-formatter.d.ts +3 -0
  50. package/dist/github/comment-formatter.js +65 -0
  51. package/dist/github/index.d.ts +5 -0
  52. package/dist/github/index.js +5 -0
  53. package/dist/github/installer.d.ts +2 -0
  54. package/dist/github/installer.js +41 -0
  55. package/dist/github/types.d.ts +40 -0
  56. package/dist/github/types.js +1 -0
  57. package/dist/github/workflow-generator.d.ts +2 -0
  58. package/dist/github/workflow-generator.js +108 -0
  59. package/dist/index.d.ts +2 -0
  60. package/dist/index.js +2 -0
  61. package/dist/reporters/console.d.ts +9 -0
  62. package/dist/reporters/console.js +76 -0
  63. package/dist/reporters/index.d.ts +6 -0
  64. package/dist/reporters/index.js +17 -0
  65. package/dist/reporters/json.d.ts +5 -0
  66. package/dist/reporters/json.js +32 -0
  67. package/dist/reporters/sarif.d.ts +9 -0
  68. package/dist/reporters/sarif.js +78 -0
  69. package/dist/reporters/types.d.ts +5 -0
  70. package/dist/reporters/types.js +1 -0
  71. package/dist/rules/config.d.ts +2 -0
  72. package/dist/rules/config.js +31 -0
  73. package/dist/rules/dependencies.d.ts +2 -0
  74. package/dist/rules/dependencies.js +32 -0
  75. package/dist/rules/docker.d.ts +2 -0
  76. package/dist/rules/docker.js +44 -0
  77. package/dist/rules/index.d.ts +5 -0
  78. package/dist/rules/index.js +25 -0
  79. package/dist/rules/kubernetes.d.ts +2 -0
  80. package/dist/rules/kubernetes.js +44 -0
  81. package/dist/rules/node.d.ts +2 -0
  82. package/dist/rules/node.js +72 -0
  83. package/dist/rules/python.d.ts +2 -0
  84. package/dist/rules/python.js +91 -0
  85. package/dist/rules/secrets.d.ts +2 -0
  86. package/dist/rules/secrets.js +82 -0
  87. package/dist/rules/types.d.ts +75 -0
  88. package/dist/rules/types.js +1 -0
  89. package/dist/utils/binary-check.d.ts +1 -0
  90. package/dist/utils/binary-check.js +10 -0
  91. package/dist/utils/line-mapper.d.ts +6 -0
  92. package/dist/utils/line-mapper.js +40 -0
  93. package/package.json +52 -0
@@ -0,0 +1,65 @@
1
+ import { isFixable } from '../fix/index.js';
2
+ export function formatPRComment(result) {
3
+ const critical = result.findings.filter(f => f.severity === 'critical');
4
+ const warning = result.findings.filter(f => f.severity === 'warning');
5
+ const fixable = result.findings.filter(f => isFixable(f));
6
+ const sections = [];
7
+ // Header
8
+ sections.push('## 🛡️ VibeGuard Security Scan\n');
9
+ // Summary table
10
+ sections.push('| Severity | Count |');
11
+ sections.push('|----------|-------|');
12
+ sections.push(`| 🔴 Critical | ${critical.length} |`);
13
+ sections.push(`| 🟡 Warning | ${warning.length} |`);
14
+ sections.push('');
15
+ // Auto-fixable callout
16
+ if (fixable.length > 0) {
17
+ sections.push(`### ✅ Auto-fixable: ${fixable.length} issues`);
18
+ sections.push('React with 👍 to this comment to automatically apply fixes.\n');
19
+ }
20
+ // Critical issues (expanded)
21
+ if (critical.length > 0) {
22
+ sections.push('### 🔴 Critical Issues\n');
23
+ critical.forEach(f => {
24
+ sections.push(`- **${f.ruleId}** \`${f.file}:${f.line}\``);
25
+ sections.push(` ${f.message}`);
26
+ if (isFixable(f)) {
27
+ sections.push(` ✅ *Auto-fixable*`);
28
+ }
29
+ sections.push('');
30
+ });
31
+ }
32
+ // Warning issues (collapsed)
33
+ if (warning.length > 0) {
34
+ sections.push('<details>');
35
+ sections.push('<summary>🟡 View warnings</summary>\n');
36
+ warning.forEach(f => {
37
+ sections.push(`- **${f.ruleId}** \`${f.file}:${f.line}\` - ${f.message}`);
38
+ });
39
+ sections.push('\n</details>\n');
40
+ }
41
+ // Footer
42
+ sections.push('---');
43
+ sections.push('<sub>Powered by [VibeGuard](https://github.com/vibeguard/vibeguard)</sub>');
44
+ return sections.join('\n');
45
+ }
46
+ export function formatInlineComment(finding) {
47
+ const lines = [];
48
+ lines.push(`🔴 **${finding.ruleId}**: ${finding.message}\n`);
49
+ if (finding.snippet) {
50
+ lines.push('```');
51
+ lines.push(finding.snippet);
52
+ lines.push('```\n');
53
+ }
54
+ if (finding.remediation) {
55
+ lines.push(`**Fix:** ${finding.remediation}\n`);
56
+ }
57
+ if (finding.cwe) {
58
+ const cweNum = finding.cwe.replace('CWE-', '');
59
+ lines.push(`[${finding.cwe}](https://cwe.mitre.org/data/definitions/${cweNum}.html)`);
60
+ }
61
+ if (isFixable(finding)) {
62
+ lines.push('\n✅ *This issue can be auto-fixed. React with 👍 to the PR comment to apply.*');
63
+ }
64
+ return lines.join('\n');
65
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './client.js';
3
+ export * from './comment-formatter.js';
4
+ export * from './workflow-generator.js';
5
+ export * from './installer.js';
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './client.js';
3
+ export * from './comment-formatter.js';
4
+ export * from './workflow-generator.js';
5
+ export * from './installer.js';
@@ -0,0 +1,2 @@
1
+ import type { InstallOptions } from './types.js';
2
+ export declare function installGitHubWorkflow(projectPath: string, options: InstallOptions): void;
@@ -0,0 +1,41 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import { generateWorkflowYAML } from './workflow-generator.js';
5
+ export function installGitHubWorkflow(projectPath, options) {
6
+ const workflowDir = join(projectPath, '.github', 'workflows');
7
+ const workflowFile = join(workflowDir, 'vibeguard.yml');
8
+ // Create directory if needed
9
+ if (!existsSync(workflowDir)) {
10
+ mkdirSync(workflowDir, { recursive: true });
11
+ }
12
+ // Check if workflow already exists
13
+ if (existsSync(workflowFile)) {
14
+ throw new Error('VibeGuard workflow already exists. Remove .github/workflows/vibeguard.yml first.');
15
+ }
16
+ // Generate workflow content
17
+ const workflow = generateWorkflowYAML(options);
18
+ // Write workflow file
19
+ writeFileSync(workflowFile, workflow);
20
+ console.log('✓ Created .github/workflows/vibeguard.yml');
21
+ // Try to detect repo
22
+ try {
23
+ const remoteUrl = execSync('git config --get remote.origin.url', {
24
+ cwd: projectPath,
25
+ encoding: 'utf-8',
26
+ stdio: ['pipe', 'pipe', 'pipe']
27
+ }).trim();
28
+ const repoMatch = remoteUrl.match(/github\.com[:/]([^/]+\/[^/.]+)/);
29
+ if (repoMatch) {
30
+ console.log(`\n📋 Next steps:`);
31
+ console.log(` 1. Commit and push: git add .github/workflows/vibeguard.yml && git commit -m "chore: add VibeGuard workflow" && git push`);
32
+ console.log(` 2. Open a test PR to see VibeGuard in action`);
33
+ if (options.autoFix) {
34
+ console.log(` 3. React with 👍 to the VibeGuard comment to auto-fix issues`);
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ console.log('\n📋 Next steps: Commit .github/workflows/vibeguard.yml and push to trigger on PRs');
40
+ }
41
+ }
@@ -0,0 +1,40 @@
1
+ export interface GitHubConfig {
2
+ token: string;
3
+ repo: string;
4
+ }
5
+ export interface InstallOptions {
6
+ autoFix: boolean;
7
+ severityThreshold: 'critical' | 'warning' | 'info';
8
+ aiVerify: boolean;
9
+ }
10
+ export interface PR {
11
+ number: number;
12
+ title: string;
13
+ state: string;
14
+ head: {
15
+ ref: string;
16
+ sha: string;
17
+ };
18
+ base: {
19
+ ref: string;
20
+ sha: string;
21
+ };
22
+ }
23
+ export interface PRFile {
24
+ filename: string;
25
+ status: string;
26
+ additions: number;
27
+ deletions: number;
28
+ changes: number;
29
+ patch?: string;
30
+ }
31
+ export interface ReviewInput {
32
+ event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT';
33
+ body?: string;
34
+ comments?: ReviewComment[];
35
+ }
36
+ export interface ReviewComment {
37
+ path: string;
38
+ line: number;
39
+ body: string;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { InstallOptions } from './types.js';
2
+ export declare function generateWorkflowYAML(options: InstallOptions): string;
@@ -0,0 +1,108 @@
1
+ export function generateWorkflowYAML(options) {
2
+ const autoFixJob = options.autoFix ? `
3
+ auto-fix:
4
+ if: |
5
+ github.event_name == 'issue_comment' &&
6
+ github.event.issue.pull_request &&
7
+ contains(github.event.comment.body, '👍')
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ with:
12
+ ref: refs/pull/\${{ github.event.issue.number }}/head
13
+ token: \${{ secrets.GITHUB_TOKEN }}
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '20'
18
+
19
+ - name: Install VibeGuard
20
+ run: npm install -g vibeguard
21
+
22
+ - name: Apply fixes
23
+ run: vibeguard fix --yes
24
+
25
+ - name: Commit fixes
26
+ run: |
27
+ git config user.name "VibeGuard Bot"
28
+ git config user.email "bot@vibeguard.dev"
29
+ git add -A
30
+ git commit -m "fix(security): apply VibeGuard auto-fixes" || echo "No changes"
31
+ git push` : '';
32
+ const aiVerifyFlag = options.aiVerify ? ' --ai' : '';
33
+ return `name: VibeGuard Security Scan
34
+
35
+ on:
36
+ pull_request:
37
+ types: [opened, synchronize, reopened]${options.autoFix ? `
38
+ issue_comment:
39
+ types: [created]` : ''}
40
+
41
+ permissions:
42
+ contents: ${options.autoFix ? 'write' : 'read'}
43
+ pull-requests: write
44
+ issues: write
45
+
46
+ jobs:
47
+ scan:
48
+ if: github.event_name == 'pull_request'
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ with:
53
+ ref: \${{ github.event.pull_request.head.ref }}
54
+
55
+ - uses: actions/setup-node@v4
56
+ with:
57
+ node-version: '20'
58
+
59
+ - name: Install VibeGuard
60
+ run: npm install -g vibeguard
61
+
62
+ - name: Scan for security issues
63
+ id: scan
64
+ run: |
65
+ vibeguard scan --json${aiVerifyFlag} --severity ${options.severityThreshold} > scan-results.json || true
66
+ echo "findings=\$(jq -r '.findings | length' scan-results.json)" >> $GITHUB_OUTPUT
67
+
68
+ - name: Post PR comment
69
+ if: steps.scan.outputs.findings != '0'
70
+ uses: actions/github-script@v7
71
+ with:
72
+ script: |
73
+ const fs = require('fs');
74
+ const results = JSON.parse(fs.readFileSync('scan-results.json'));
75
+
76
+ const critical = results.findings.filter(f => f.severity === 'critical').length;
77
+ const warning = results.findings.filter(f => f.severity === 'warning').length;
78
+ const fixable = results.findings.filter(f => f.fixable).length;
79
+
80
+ const body = \`## 🛡️ VibeGuard Security Scan
81
+
82
+ | Severity | Count |
83
+ |----------|-------|
84
+ | 🔴 Critical | \${critical} |
85
+ | 🟡 Warning | \${warning} |
86
+
87
+ \${fixable > 0 ? \`### ✅ Auto-fixable: \${fixable} issues
88
+
89
+ React with 👍 to this comment to automatically apply fixes.
90
+ \` : ''}
91
+ <details>
92
+ <summary>View all findings</summary>
93
+
94
+ \${results.findings.map(f => \`- **\${f.ruleId}** \\\`\${f.file}:\${f.line}\\\` - \${f.message}\`).join('\\n')}
95
+
96
+ </details>
97
+
98
+ ---
99
+ <sub>Powered by [VibeGuard](https://github.com/vibeguard/vibeguard)</sub>\`;
100
+
101
+ await github.rest.issues.createComment({
102
+ ...context.repo,
103
+ issue_number: context.issue.number,
104
+ body
105
+ });
106
+ ${autoFixJob}
107
+ `;
108
+ }
@@ -0,0 +1,2 @@
1
+ export declare const VERSION = "1.0.0";
2
+ export declare const NAME = "vibeguard";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export const VERSION = '1.0.0';
2
+ export const NAME = 'vibeguard';
@@ -0,0 +1,9 @@
1
+ import type { ScanResult } from '../rules/types.js';
2
+ import type { Reporter } from './types.js';
3
+ export declare class ConsoleReporter implements Reporter {
4
+ private noColor;
5
+ constructor(noColor?: boolean);
6
+ report(result: ScanResult): string;
7
+ private formatFinding;
8
+ private style;
9
+ }
@@ -0,0 +1,76 @@
1
+ import chalk from 'chalk';
2
+ export class ConsoleReporter {
3
+ noColor;
4
+ constructor(noColor = false) {
5
+ this.noColor = noColor;
6
+ }
7
+ report(result) {
8
+ const lines = [];
9
+ lines.push('');
10
+ lines.push(this.style(' VibeGuard v1.0.0', 'bold'));
11
+ lines.push('');
12
+ lines.push(` Scanning complete`);
13
+ lines.push(` Files: ${result.scannedFiles} scanned, ${result.skippedFiles} skipped`);
14
+ lines.push('');
15
+ if (result.findings.length === 0) {
16
+ lines.push(this.style(' ✓ No issues found', 'green'));
17
+ }
18
+ else {
19
+ const sorted = [...result.findings].sort((a, b) => {
20
+ const severityOrder = { critical: 0, warning: 1, info: 2 };
21
+ return severityOrder[a.severity] - severityOrder[b.severity];
22
+ });
23
+ for (const finding of sorted) {
24
+ lines.push(this.formatFinding(finding));
25
+ lines.push('');
26
+ }
27
+ }
28
+ lines.push(' ' + '─'.repeat(40));
29
+ lines.push(` Summary: ${this.style(String(result.criticalCount) + ' critical', 'red')}, ` +
30
+ `${this.style(String(result.warningCount) + ' warnings', 'yellow')}, ` +
31
+ `${result.infoCount} info`);
32
+ lines.push(` Duration: ${(result.duration / 1000).toFixed(1)}s`);
33
+ lines.push('');
34
+ return lines.join('\n');
35
+ }
36
+ formatFinding(finding) {
37
+ const lines = [];
38
+ const severityLabel = finding.severity.toUpperCase();
39
+ const severityStyled = finding.severity === 'critical'
40
+ ? this.style(severityLabel, 'redBold')
41
+ : finding.severity === 'warning'
42
+ ? this.style(severityLabel, 'yellow')
43
+ : this.style(severityLabel, 'blue');
44
+ lines.push(` ${severityStyled} ${this.style(finding.file + ':' + finding.line + ':' + finding.column, 'cyan')}`);
45
+ lines.push(` ${this.style(finding.ruleId, 'dim')} ${finding.title}`);
46
+ lines.push(` → ${finding.snippet}`);
47
+ if (finding.remediation) {
48
+ lines.push(` ${this.style('Remediation:', 'dim')} ${finding.remediation}`);
49
+ }
50
+ return lines.join('\n');
51
+ }
52
+ style(text, style) {
53
+ if (this.noColor)
54
+ return text;
55
+ switch (style) {
56
+ case 'bold':
57
+ return chalk.bold(text);
58
+ case 'red':
59
+ return chalk.red(text);
60
+ case 'redBold':
61
+ return chalk.red.bold(text);
62
+ case 'yellow':
63
+ return chalk.yellow(text);
64
+ case 'blue':
65
+ return chalk.blue(text);
66
+ case 'cyan':
67
+ return chalk.cyan(text);
68
+ case 'dim':
69
+ return chalk.dim(text);
70
+ case 'green':
71
+ return chalk.green(text);
72
+ default:
73
+ return text;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,6 @@
1
+ import type { Reporter, ReporterType } from './types.js';
2
+ export declare function createReporter(type: ReporterType, noColor?: boolean): Reporter;
3
+ export { type Reporter, type ReporterType } from './types.js';
4
+ export { ConsoleReporter } from './console.js';
5
+ export { JsonReporter } from './json.js';
6
+ export { SarifReporter } from './sarif.js';
@@ -0,0 +1,17 @@
1
+ import { ConsoleReporter } from './console.js';
2
+ import { JsonReporter } from './json.js';
3
+ import { SarifReporter } from './sarif.js';
4
+ export function createReporter(type, noColor = false) {
5
+ switch (type) {
6
+ case 'json':
7
+ return new JsonReporter();
8
+ case 'sarif':
9
+ return new SarifReporter();
10
+ case 'console':
11
+ default:
12
+ return new ConsoleReporter(noColor);
13
+ }
14
+ }
15
+ export { ConsoleReporter } from './console.js';
16
+ export { JsonReporter } from './json.js';
17
+ export { SarifReporter } from './sarif.js';
@@ -0,0 +1,5 @@
1
+ import type { ScanResult } from '../rules/types.js';
2
+ import type { Reporter } from './types.js';
3
+ export declare class JsonReporter implements Reporter {
4
+ report(result: ScanResult): string;
5
+ }
@@ -0,0 +1,32 @@
1
+ import { VERSION } from '../index.js';
2
+ export class JsonReporter {
3
+ report(result) {
4
+ const output = {
5
+ version: VERSION,
6
+ timestamp: result.timestamp,
7
+ summary: {
8
+ scannedFiles: result.scannedFiles,
9
+ skippedFiles: result.skippedFiles,
10
+ critical: result.criticalCount,
11
+ warning: result.warningCount,
12
+ info: result.infoCount,
13
+ duration: result.duration,
14
+ },
15
+ findings: result.findings.map((f) => ({
16
+ ruleId: f.ruleId,
17
+ title: f.title,
18
+ severity: f.severity,
19
+ category: f.category,
20
+ file: f.file,
21
+ line: f.line,
22
+ column: f.column,
23
+ snippet: f.snippet,
24
+ message: f.message,
25
+ remediation: f.remediation,
26
+ cwe: f.cwe,
27
+ owasp: f.owasp,
28
+ })),
29
+ };
30
+ return JSON.stringify(output, null, 2);
31
+ }
32
+ }
@@ -0,0 +1,9 @@
1
+ import type { ScanResult } from '../rules/types.js';
2
+ import type { Reporter } from './types.js';
3
+ export declare class SarifReporter implements Reporter {
4
+ report(result: ScanResult): string;
5
+ private buildRules;
6
+ private buildResults;
7
+ private mapSeverity;
8
+ private getSeverityScore;
9
+ }
@@ -0,0 +1,78 @@
1
+ import { VERSION } from '../index.js';
2
+ import { allRules } from '../rules/index.js';
3
+ export class SarifReporter {
4
+ report(result) {
5
+ const rules = this.buildRules(result.findings);
6
+ const results = this.buildResults(result.findings);
7
+ const sarif = {
8
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
9
+ version: '2.1.0',
10
+ runs: [
11
+ {
12
+ tool: {
13
+ driver: {
14
+ name: 'VibeGuard',
15
+ version: VERSION,
16
+ informationUri: 'https://github.com/vibeguard/vibeguard',
17
+ rules,
18
+ },
19
+ },
20
+ results,
21
+ },
22
+ ],
23
+ };
24
+ return JSON.stringify(sarif, null, 2);
25
+ }
26
+ buildRules(findings) {
27
+ const ruleIds = [...new Set(findings.map((f) => f.ruleId))];
28
+ return ruleIds.map((id) => {
29
+ const rule = allRules.find((r) => r.id === id);
30
+ const finding = findings.find((f) => f.ruleId === id);
31
+ return {
32
+ id,
33
+ name: rule?.title || finding?.title || id,
34
+ shortDescription: { text: rule?.message || finding?.message || '' },
35
+ fullDescription: rule?.remediation ? { text: rule.remediation } : undefined,
36
+ properties: {
37
+ security_severity: this.getSeverityScore(finding?.severity || 'info'),
38
+ tags: rule?.cwe ? [rule.cwe] : [],
39
+ },
40
+ };
41
+ });
42
+ }
43
+ buildResults(findings) {
44
+ return findings.map((f) => ({
45
+ ruleId: f.ruleId,
46
+ level: this.mapSeverity(f.severity),
47
+ message: { text: `${f.message}\n\nSnippet: ${f.snippet}` },
48
+ locations: [
49
+ {
50
+ physicalLocation: {
51
+ artifactLocation: { uri: f.file },
52
+ region: { startLine: f.line, startColumn: f.column },
53
+ },
54
+ },
55
+ ],
56
+ }));
57
+ }
58
+ mapSeverity(severity) {
59
+ switch (severity) {
60
+ case 'critical':
61
+ return 'error';
62
+ case 'warning':
63
+ return 'warning';
64
+ default:
65
+ return 'note';
66
+ }
67
+ }
68
+ getSeverityScore(severity) {
69
+ switch (severity) {
70
+ case 'critical':
71
+ return '9.0';
72
+ case 'warning':
73
+ return '6.0';
74
+ default:
75
+ return '3.0';
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,5 @@
1
+ import type { ScanResult } from '../rules/types.js';
2
+ export interface Reporter {
3
+ report(result: ScanResult): string;
4
+ }
5
+ export type ReporterType = 'console' | 'json' | 'sarif';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { DetectionRule } from './types.js';
2
+ export declare const configRules: DetectionRule[];
@@ -0,0 +1,31 @@
1
+ export const configRules = [
2
+ {
3
+ id: 'VG-CFG-001',
4
+ title: 'Service Bound to 0.0.0.0',
5
+ severity: 'warning',
6
+ category: 'config',
7
+ languages: ['node', 'typescript', 'python'],
8
+ filePatterns: ['*.js', '*.ts', '*.mjs', '*.cjs', '*.py'],
9
+ pattern: /(?:host\s*[:=]\s*['"]0\.0\.0\.0['"]|\.listen\s*\([^)]*['"]0\.0\.0\.0['"])/g,
10
+ message: 'Service binds to all interfaces. May expose internal services externally.',
11
+ remediation: 'Bind to 127.0.0.1 for local-only services. Use reverse proxy for external access.',
12
+ confidence: 'medium',
13
+ cwe: 'CWE-668',
14
+ owasp: 'A05:2021',
15
+ aiVerification: { enabled: true },
16
+ },
17
+ {
18
+ id: 'VG-CFG-002',
19
+ title: 'Public S3 Bucket ACL',
20
+ severity: 'critical',
21
+ category: 'config',
22
+ languages: ['node', 'typescript', 'python', 'yaml', 'json'],
23
+ filePatterns: ['*.js', '*.ts', '*.py', '*.yml', '*.yaml', '*.json', '*.tf'],
24
+ pattern: /(?:ACL['":\s]+public-read|"Principal"\s*:\s*"\*")/g,
25
+ message: 'S3 bucket configured for public access. Data exposed to internet.',
26
+ remediation: 'Remove public-read ACL. Use private buckets with signed URLs.',
27
+ confidence: 'high',
28
+ cwe: 'CWE-732',
29
+ owasp: 'A01:2021',
30
+ },
31
+ ];
@@ -0,0 +1,2 @@
1
+ import type { DetectionRule } from './types.js';
2
+ export declare const dependencyRules: DetectionRule[];
@@ -0,0 +1,32 @@
1
+ export const dependencyRules = [
2
+ {
3
+ id: 'VG-DEP-001',
4
+ title: 'Ghost Dependency (PyPI)',
5
+ severity: 'critical',
6
+ category: 'dependency',
7
+ languages: ['python'],
8
+ filePatterns: ['*.py', 'requirements.txt', 'Pipfile', 'pyproject.toml'],
9
+ pattern: /(?:from|import)\s+(secure[-_]|enterprise[-_]|flask[-_]admin[-_]|langchain[a-z]+)/g,
10
+ message: 'Potentially hallucinated package name. Attacker may have registered malicious package.',
11
+ remediation: 'Verify package exists on PyPI. Check package ownership and downloads.',
12
+ confidence: 'medium',
13
+ cwe: 'CWE-829',
14
+ owasp: 'A06:2021',
15
+ aiVerification: { enabled: true },
16
+ },
17
+ {
18
+ id: 'VG-DEP-002',
19
+ title: 'Ghost Dependency (npm)',
20
+ severity: 'critical',
21
+ category: 'dependency',
22
+ languages: ['node', 'typescript'],
23
+ filePatterns: ['*.js', '*.ts', '*.mjs', '*.cjs', 'package.json'],
24
+ pattern: /(?:require|import)\s*\(?['"](?:huggingface[-_]|@enterprise\/|react[-_]native[-_]toolkit)/g,
25
+ message: 'Potentially hallucinated package name. Attacker may have registered malicious package.',
26
+ remediation: 'Verify package exists on npm. Check package ownership and weekly downloads.',
27
+ confidence: 'medium',
28
+ cwe: 'CWE-829',
29
+ owasp: 'A06:2021',
30
+ aiVerification: { enabled: true },
31
+ },
32
+ ];
@@ -0,0 +1,2 @@
1
+ import type { DetectionRule } from './types.js';
2
+ export declare const dockerRules: DetectionRule[];
@@ -0,0 +1,44 @@
1
+ export const dockerRules = [
2
+ {
3
+ id: 'VG-DOCK-001',
4
+ title: 'Container Running as Root',
5
+ severity: 'warning',
6
+ category: 'docker',
7
+ languages: ['docker'],
8
+ filePatterns: ['Dockerfile', '*.dockerfile', 'Dockerfile.*'],
9
+ pattern: /^(?!.*\bUSER\b).*$/gm,
10
+ message: 'Dockerfile has no USER directive. Container runs as root by default.',
11
+ remediation: 'Add USER directive to run as non-root. Example: USER 1000:1000',
12
+ confidence: 'medium',
13
+ cwe: 'CWE-250',
14
+ owasp: 'A05:2021',
15
+ },
16
+ {
17
+ id: 'VG-DOCK-002',
18
+ title: 'Docker Socket Exposed',
19
+ severity: 'critical',
20
+ category: 'docker',
21
+ languages: ['docker', 'yaml'],
22
+ filePatterns: ['docker-compose.yml', 'docker-compose.yaml', '*.yml', '*.yaml'],
23
+ pattern: /\/var\/run\/docker\.sock/g,
24
+ message: 'Docker socket mounted into container. Grants full host control.',
25
+ remediation: 'Remove docker.sock mount. Use Docker API over TCP with TLS if needed.',
26
+ confidence: 'high',
27
+ cwe: 'CWE-269',
28
+ owasp: 'A05:2021',
29
+ },
30
+ {
31
+ id: 'VG-DOCK-003',
32
+ title: 'Privileged Container',
33
+ severity: 'critical',
34
+ category: 'docker',
35
+ languages: ['docker', 'yaml', 'kubernetes'],
36
+ filePatterns: ['docker-compose.yml', 'docker-compose.yaml', '*.yml', '*.yaml'],
37
+ pattern: /(?:--privileged|privileged\s*:\s*true)/g,
38
+ message: 'Privileged container has full host access. Attacker can escape container.',
39
+ remediation: 'Remove privileged flag. Use specific capabilities if needed.',
40
+ confidence: 'high',
41
+ cwe: 'CWE-250',
42
+ owasp: 'A05:2021',
43
+ },
44
+ ];