vibe-shield 1.0.0 → 1.1.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.
package/dist/cli.js CHANGED
@@ -1,130 +1,227 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/scanner.ts
4
- import { readFileSync, readdirSync, statSync } from "fs";
4
+ import { readFileSync, readdirSync, statSync, existsSync } from "fs";
5
5
  import { join, extname } from "path";
6
6
 
7
7
  // src/patterns.ts
8
8
  var securityPatterns = [
9
+ {
10
+ id: "aws-access-key",
11
+ name: "AWS Access Key",
12
+ regex: /['"`](AKIA[0-9A-Z]{16})['"`]/g,
13
+ fixPrompt: "AWS access key detected. Remove immediately and rotate in AWS console. Use environment variables or IAM roles.",
14
+ severity: "critical"
15
+ },
16
+ {
17
+ id: "aws-secret-key",
18
+ name: "AWS Secret Key",
19
+ regex: /aws_?secret_?access_?key\s*[:=]\s*['"`][A-Za-z0-9\/+=]{40}['"`]/gi,
20
+ fixPrompt: "AWS secret key detected. Remove immediately and rotate in AWS console. Use environment variables.",
21
+ severity: "critical"
22
+ },
23
+ {
24
+ id: "openai-key",
25
+ name: "OpenAI API Key",
26
+ regex: /['"`](sk-[A-Za-z0-9]{48,})['"`]/g,
27
+ fixPrompt: "OpenAI API key detected. Remove and rotate at platform.openai.com. Use process.env.OPENAI_API_KEY.",
28
+ severity: "critical"
29
+ },
30
+ {
31
+ id: "anthropic-key",
32
+ name: "Anthropic API Key",
33
+ regex: /['"`](sk-ant-[A-Za-z0-9\-]{80,})['"`]/g,
34
+ fixPrompt: "Anthropic API key detected. Remove and rotate at console.anthropic.com. Use process.env.ANTHROPIC_API_KEY.",
35
+ severity: "critical"
36
+ },
37
+ {
38
+ id: "stripe-secret",
39
+ name: "Stripe Secret Key",
40
+ regex: /['"`](sk_live_[A-Za-z0-9]{24,})['"`]/g,
41
+ fixPrompt: "Stripe live secret key detected. Remove and rotate in Stripe dashboard. Use process.env.STRIPE_SECRET_KEY.",
42
+ severity: "critical"
43
+ },
44
+ {
45
+ id: "stripe-restricted",
46
+ name: "Stripe Restricted Key",
47
+ regex: /['"`](rk_live_[A-Za-z0-9]{24,})['"`]/g,
48
+ fixPrompt: "Stripe restricted key detected. Remove and rotate in Stripe dashboard. Use environment variables.",
49
+ severity: "critical"
50
+ },
51
+ {
52
+ id: "github-token",
53
+ name: "GitHub Token",
54
+ regex: /['"`](ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,})['"`]/g,
55
+ fixPrompt: "GitHub token detected. Remove and rotate at github.com/settings/tokens. Use process.env.GITHUB_TOKEN.",
56
+ severity: "critical"
57
+ },
58
+ {
59
+ id: "slack-token",
60
+ name: "Slack Token",
61
+ regex: /['"`](xox[baprs]-[A-Za-z0-9\-]{10,})['"`]/g,
62
+ fixPrompt: "Slack token detected. Remove and rotate in Slack app settings. Use environment variables.",
63
+ severity: "critical"
64
+ },
65
+ {
66
+ id: "twilio-key",
67
+ name: "Twilio API Key",
68
+ regex: /['"`](SK[A-Za-z0-9]{32})['"`]/g,
69
+ fixPrompt: "Twilio API key detected. Remove and rotate in Twilio console. Use environment variables.",
70
+ severity: "critical"
71
+ },
72
+ {
73
+ id: "sendgrid-key",
74
+ name: "SendGrid API Key",
75
+ regex: /['"`](SG\.[A-Za-z0-9\-_]{22}\.[A-Za-z0-9\-_]{43})['"`]/g,
76
+ fixPrompt: "SendGrid API key detected. Remove and rotate in SendGrid dashboard. Use environment variables.",
77
+ severity: "critical"
78
+ },
79
+ {
80
+ id: "private-key",
81
+ name: "Private Key",
82
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
83
+ fixPrompt: "Private key detected in code. Move to a secure file outside repo or use a secrets manager.",
84
+ severity: "critical"
85
+ },
9
86
  {
10
87
  id: "hardcoded-secret",
11
88
  name: "Hardcoded Secret",
12
89
  regex: /(?:api_?key|api_?secret|secret_?key|auth_?token|access_?token|private_?key|client_?secret)\s*[:=]\s*['"`][A-Za-z0-9_\-\.\/\+]{8,}['"`]/gi,
13
- fixPrompt: "Move this secret to an environment variable. Add it to .env and use process.env.YOUR_SECRET. Add .env to .gitignore."
90
+ fixPrompt: "Move this secret to an environment variable. Add to .env and use process.env.YOUR_SECRET. Add .env to .gitignore.",
91
+ severity: "high"
14
92
  },
15
93
  {
16
94
  id: "hardcoded-password",
17
95
  name: "Hardcoded Password",
18
96
  regex: /(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`\s]{4,}['"`]/gi,
19
- fixPrompt: "Move this password to an environment variable. Never commit passwords to version control."
20
- },
21
- {
22
- id: "aws-key",
23
- name: "AWS Access Key",
24
- regex: /['"`](AKIA[0-9A-Z]{16})['"`]/g,
25
- fixPrompt: "AWS access key detected. Remove it immediately and rotate the key in AWS console. Use environment variables or AWS IAM roles."
97
+ fixPrompt: "Move this password to an environment variable. Never commit passwords to version control.",
98
+ severity: "high"
26
99
  },
27
100
  {
28
101
  id: "jwt-secret-inline",
29
102
  name: "Hardcoded JWT Secret",
30
103
  regex: /jwt\.sign\s*\([^)]+,\s*['"`][^'"`]{8,}['"`]/gi,
31
- fixPrompt: "Move JWT secret to an environment variable. Hardcoded secrets get committed to version control."
104
+ fixPrompt: "Move JWT secret to an environment variable. Hardcoded secrets get committed to version control.",
105
+ severity: "high"
106
+ },
107
+ {
108
+ id: "database-url",
109
+ name: "Database Connection String",
110
+ regex: /['"`](mongodb(\+srv)?|postgres(ql)?|mysql|redis):\/\/[^'"`\s]{10,}['"`]/gi,
111
+ fixPrompt: "Database connection string with credentials detected. Use process.env.DATABASE_URL.",
112
+ severity: "high"
32
113
  },
33
114
  {
34
115
  id: "sql-injection-template",
35
116
  name: "SQL Injection",
36
117
  regex: /(?:query|execute)\s*\(\s*`(?:SELECT|INSERT|UPDATE|DELETE|DROP)[^`]*\$\{/gi,
37
- fixPrompt: "Use parameterized queries instead of template literals. Example: query('SELECT * FROM users WHERE id = ?', [userId])"
118
+ fixPrompt: "Use parameterized queries instead of template literals. Example: query('SELECT * FROM users WHERE id = ?', [userId])",
119
+ severity: "high"
38
120
  },
39
121
  {
40
122
  id: "sql-injection-concat",
41
123
  name: "SQL Injection",
42
124
  regex: /(?:query|execute)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*['"]\s*\+/gi,
43
- fixPrompt: "Never concatenate variables into SQL strings. Use parameterized queries with placeholders."
125
+ fixPrompt: "Never concatenate variables into SQL strings. Use parameterized queries with placeholders.",
126
+ severity: "high"
44
127
  },
45
128
  {
46
129
  id: "command-injection",
47
130
  name: "Command Injection",
48
131
  regex: /(?:exec|execSync)\s*\(\s*`[^`]*\$\{/g,
49
- fixPrompt: "Avoid template literals in shell commands. Use spawn() with an array of arguments, or use a library like shell-escape."
132
+ fixPrompt: "Avoid template literals in shell commands. Use spawn() with an array of arguments.",
133
+ severity: "critical"
50
134
  },
51
135
  {
52
136
  id: "command-injection-concat",
53
137
  name: "Command Injection",
54
138
  regex: /(?:exec|execSync)\s*\([^)]*\+\s*(?:req\.|user|input|param|query|body)/gi,
55
- fixPrompt: "User input in shell commands allows arbitrary command execution. Use spawn() with argument arrays."
139
+ fixPrompt: "User input in shell commands allows arbitrary command execution. Use spawn() with argument arrays.",
140
+ severity: "critical"
56
141
  },
57
142
  {
58
143
  id: "eval-usage",
59
144
  name: "Dangerous eval()",
60
145
  regex: /[=:]\s*eval\s*\(\s*(?:req\.|user|input|param|query|body|data)/gi,
61
- fixPrompt: "eval() with user input allows arbitrary code execution. Use JSON.parse() for JSON, or refactor to avoid eval entirely."
146
+ fixPrompt: "eval() with user input allows arbitrary code execution. Use JSON.parse() for JSON, or refactor to avoid eval.",
147
+ severity: "high"
62
148
  },
63
149
  {
64
150
  id: "new-function",
65
151
  name: "Dangerous Function Constructor",
66
152
  regex: /new\s+Function\s*\([^)]*(?:req\.|user|input|param|query|body)/gi,
67
- fixPrompt: "new Function() with user input is as dangerous as eval(). Refactor to avoid dynamic code generation."
153
+ fixPrompt: "new Function() with user input is as dangerous as eval(). Refactor to avoid dynamic code generation.",
154
+ severity: "high"
68
155
  },
69
156
  {
70
157
  id: "innerhtml-variable",
71
158
  name: "XSS via innerHTML",
72
159
  regex: /\.innerHTML\s*=\s*(?:req\.|user|input|param|query|body|data|props\.|this\.)/gi,
73
- fixPrompt: "Setting innerHTML with user data enables XSS attacks. Use textContent or sanitize with DOMPurify."
160
+ fixPrompt: "Setting innerHTML with user data enables XSS attacks. Use textContent or sanitize with DOMPurify.",
161
+ severity: "high"
74
162
  },
75
163
  {
76
164
  id: "react-dangerous-html",
77
165
  name: "React XSS Risk",
78
166
  regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?:props\.|this\.|data|user|input)/gi,
79
- fixPrompt: "dangerouslySetInnerHTML with user data enables XSS. Sanitize HTML with DOMPurify first."
167
+ fixPrompt: "dangerouslySetInnerHTML with user data enables XSS. Sanitize HTML with DOMPurify first.",
168
+ severity: "high"
80
169
  },
81
170
  {
82
171
  id: "weak-hash-md5",
83
172
  name: "Weak Hash (MD5)",
84
173
  regex: /createHash\s*\(\s*['"`]md5['"`]\s*\)/g,
85
- fixPrompt: "MD5 is broken. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2."
174
+ fixPrompt: "MD5 is broken. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2.",
175
+ severity: "medium"
86
176
  },
87
177
  {
88
178
  id: "weak-hash-sha1",
89
179
  name: "Weak Hash (SHA1)",
90
180
  regex: /createHash\s*\(\s*['"`]sha1['"`]\s*\)/g,
91
- fixPrompt: "SHA1 is deprecated for security. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2."
181
+ fixPrompt: "SHA1 is deprecated for security. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2.",
182
+ severity: "medium"
92
183
  },
93
184
  {
94
185
  id: "ssl-disabled",
95
186
  name: "SSL Verification Disabled",
96
187
  regex: /rejectUnauthorized\s*:\s*false/g,
97
- fixPrompt: "Disabling SSL verification allows man-in-the-middle attacks. Remove this or set to true."
188
+ fixPrompt: "Disabling SSL verification allows man-in-the-middle attacks. Remove this or set to true.",
189
+ severity: "medium"
98
190
  },
99
191
  {
100
192
  id: "cors-wildcard",
101
193
  name: "CORS Allows All Origins",
102
194
  regex: /['"`]Access-Control-Allow-Origin['"`]\s*[,:]\s*['"`]\*['"`]/g,
103
- fixPrompt: "CORS wildcard (*) allows any website to make requests. Specify allowed origins explicitly."
195
+ fixPrompt: "CORS wildcard (*) allows any website to make requests. Specify allowed origins explicitly.",
196
+ severity: "medium"
104
197
  },
105
198
  {
106
199
  id: "path-traversal",
107
200
  name: "Path Traversal Risk",
108
201
  regex: /(?:readFile|writeFile|readFileSync|writeFileSync)\s*\(\s*(?:req\.|user|input|param|query|body)/gi,
109
- fixPrompt: "User input in file paths allows reading/writing arbitrary files. Validate paths with path.resolve() and check they're within allowed directories."
202
+ fixPrompt: "User input in file paths allows reading/writing arbitrary files. Validate paths with path.resolve() and check they're within allowed directories.",
203
+ severity: "high"
110
204
  },
111
205
  {
112
206
  id: "nosql-where",
113
207
  name: "NoSQL Injection ($where)",
114
208
  regex: /\.(find|findOne)\s*\(\s*\{[^}]*\$where\s*:/g,
115
- fixPrompt: "$where executes JavaScript and enables NoSQL injection. Use standard MongoDB query operators."
209
+ fixPrompt: "$where executes JavaScript and enables NoSQL injection. Use standard MongoDB query operators.",
210
+ severity: "high"
116
211
  },
117
212
  {
118
213
  id: "pickle-load",
119
214
  name: "Insecure Pickle (Python)",
120
215
  regex: /pickle\.loads?\s*\(\s*(?:request|user|input|data|file)/gi,
121
- fixPrompt: "pickle.load with untrusted data allows arbitrary code execution. Use JSON for untrusted data."
216
+ fixPrompt: "pickle.load with untrusted data allows arbitrary code execution. Use JSON for untrusted data.",
217
+ severity: "critical"
122
218
  },
123
219
  {
124
220
  id: "python-shell",
125
221
  name: "Shell Injection (Python)",
126
222
  regex: /subprocess\.\w+\s*\([^)]*shell\s*=\s*True[^)]*(?:request|user|input|param)/gi,
127
- fixPrompt: "shell=True with user input enables command injection. Pass command as a list without shell=True."
223
+ fixPrompt: "shell=True with user input enables command injection. Pass command as a list without shell=True.",
224
+ severity: "critical"
128
225
  }
129
226
  ];
130
227
 
@@ -186,6 +283,29 @@ function getLineNumber(content, matchIndex) {
186
283
  `);
187
284
  return lines.length;
188
285
  }
286
+ function checkGitignore(dir) {
287
+ const warnings = [];
288
+ const gitignorePath = join(dir, ".gitignore");
289
+ const envPath = join(dir, ".env");
290
+ if (!existsSync(envPath)) {
291
+ return warnings;
292
+ }
293
+ if (!existsSync(gitignorePath)) {
294
+ warnings.push(".env file exists but no .gitignore found. Create a .gitignore and add .env to prevent committing secrets.");
295
+ return warnings;
296
+ }
297
+ try {
298
+ const gitignoreContent = readFileSync(gitignorePath, "utf-8");
299
+ const lines = gitignoreContent.split(`
300
+ `).map((l) => l.trim());
301
+ const envIgnored = lines.some((line) => line === ".env" || line === ".env*" || line === "*.env" || line === ".env.local" || line.startsWith(".env"));
302
+ if (!envIgnored) {
303
+ warnings.push(".env file exists but is not in .gitignore. Add .env to .gitignore to prevent committing secrets.");
304
+ }
305
+ } catch {
306
+ }
307
+ return warnings;
308
+ }
189
309
  function scanFile(filePath) {
190
310
  const issues = [];
191
311
  let content;
@@ -206,7 +326,8 @@ function scanFile(filePath) {
206
326
  patternId: pattern.id,
207
327
  patternName: pattern.name,
208
328
  fixPrompt: pattern.fixPrompt,
209
- match: matchText.length > 60 ? matchText.substring(0, 60) + "..." : matchText
329
+ match: matchText.length > 60 ? matchText.substring(0, 60) + "..." : matchText,
330
+ severity: pattern.severity
210
331
  });
211
332
  }
212
333
  }
@@ -215,15 +336,25 @@ function scanFile(filePath) {
215
336
  function scanFiles(dir) {
216
337
  const files = collectFiles(dir);
217
338
  const allIssues = [];
339
+ const warnings = checkGitignore(dir);
218
340
  for (const file of files) {
219
341
  const issues = scanFile(file);
220
342
  allIssues.push(...issues);
221
343
  }
222
- return allIssues;
344
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
345
+ allIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
346
+ return { issues: allIssues, warnings };
223
347
  }
224
348
 
225
349
  // src/prompter.ts
226
- function formatAgentPrompt(issues) {
350
+ var severityColors = {
351
+ critical: "\x1B[31m",
352
+ high: "\x1B[33m",
353
+ medium: "\x1B[36m",
354
+ low: "\x1B[37m"
355
+ };
356
+ var reset = "\x1B[0m";
357
+ function formatAgentPrompt(issues, useColors = true) {
227
358
  if (!issues || issues.length === 0) {
228
359
  return "";
229
360
  }
@@ -234,7 +365,10 @@ function formatAgentPrompt(issues) {
234
365
  ""
235
366
  ];
236
367
  issues.forEach((issue, index) => {
237
- lines.push(`[TASK ${index + 1}] Fix ${issue.patternName} in ${issue.file} at line ${issue.line}`);
368
+ const severityLabel = issue.severity.toUpperCase();
369
+ const color = useColors ? severityColors[issue.severity] : "";
370
+ const colorReset = useColors ? reset : "";
371
+ lines.push(`${color}[${severityLabel}]${colorReset} [TASK ${index + 1}] Fix ${issue.patternName} in ${issue.file} at line ${issue.line}`);
238
372
  lines.push(`[FOUND]: ${issue.match}`);
239
373
  lines.push(`[INSTRUCTION]: ${issue.fixPrompt}`);
240
374
  lines.push("");
@@ -255,9 +389,39 @@ function generateSummary(issues) {
255
389
  }
256
390
  return summary;
257
391
  }
392
+ function generateSeveritySummary(issues) {
393
+ const summary = {
394
+ critical: 0,
395
+ high: 0,
396
+ medium: 0,
397
+ low: 0
398
+ };
399
+ for (const issue of issues) {
400
+ summary[issue.severity]++;
401
+ }
402
+ return summary;
403
+ }
404
+ function formatJson(issues, warnings) {
405
+ return JSON.stringify({
406
+ summary: {
407
+ total: issues.length,
408
+ bySeverity: generateSeveritySummary(issues),
409
+ byType: generateSummary(issues)
410
+ },
411
+ issues: issues.map((issue) => ({
412
+ severity: issue.severity,
413
+ type: issue.patternName,
414
+ file: issue.file,
415
+ line: issue.line,
416
+ match: issue.match,
417
+ fix: issue.fixPrompt
418
+ })),
419
+ warnings
420
+ }, null, 2);
421
+ }
258
422
 
259
423
  // src/init.ts
260
- import { writeFileSync, existsSync, readFileSync as readFileSync2 } from "fs";
424
+ import { writeFileSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
261
425
  import { join as join2 } from "path";
262
426
  var CURSORRULES_CONTENT = `# Vibe Shield Security Rules
263
427
 
@@ -272,7 +436,7 @@ Before marking a task as done, run \`npx vibe-shield\`. If issues are found, fol
272
436
  `;
273
437
  function initVibeShield(dir) {
274
438
  const cursorrulesPath = join2(dir, ".cursorrules");
275
- if (existsSync(cursorrulesPath)) {
439
+ if (existsSync2(cursorrulesPath)) {
276
440
  try {
277
441
  const existingContent = readFileSync2(cursorrulesPath, "utf-8");
278
442
  if (existingContent.includes("vibe-shield") || existingContent.includes("Vibe Shield")) {
@@ -316,7 +480,7 @@ function initVibeShield(dir) {
316
480
  }
317
481
 
318
482
  // src/cli.ts
319
- var VERSION = "1.0.0";
483
+ var VERSION = "1.1.0";
320
484
  var colors = {
321
485
  reset: "\x1B[0m",
322
486
  cyan: "\x1B[36m",
@@ -324,7 +488,8 @@ var colors = {
324
488
  red: "\x1B[31m",
325
489
  yellow: "\x1B[33m",
326
490
  dim: "\x1B[2m",
327
- bold: "\x1B[1m"
491
+ bold: "\x1B[1m",
492
+ magenta: "\x1B[35m"
328
493
  };
329
494
  function printBanner() {
330
495
  console.log(colors.cyan + `
@@ -345,10 +510,14 @@ function printHelp() {
345
510
  console.log(colors.green + " init" + colors.reset + colors.dim + " Create .cursorrules for AI agent integration" + colors.reset);
346
511
  console.log(colors.green + " help" + colors.reset + colors.dim + " Show this help message" + colors.reset);
347
512
  console.log(colors.green + " version" + colors.reset + colors.dim + ` Show version number
513
+ ` + colors.reset);
514
+ console.log("Options:");
515
+ console.log(colors.green + " --json" + colors.reset + colors.dim + ` Output results as JSON
348
516
  ` + colors.reset);
349
517
  console.log("Examples:");
350
518
  console.log(colors.dim + " npx vibe-shield" + colors.reset);
351
519
  console.log(colors.dim + " npx vibe-shield scan ./src" + colors.reset);
520
+ console.log(colors.dim + " npx vibe-shield scan . --json" + colors.reset);
352
521
  console.log(colors.dim + ` npx vibe-shield init
353
522
  ` + colors.reset);
354
523
  }
@@ -362,32 +531,60 @@ function runInit(dir) {
362
531
  const result = initVibeShield(dir);
363
532
  if (result.success) {
364
533
  console.log(colors.green + "✓ " + colors.reset + result.message);
365
- console.log(colors.dim + ` Created: ${result.path}
534
+ console.log(colors.dim + ` Path: ${result.path}
366
535
  ` + colors.reset);
367
536
  console.log("Your AI agent will now run vibe-shield before completing tasks.");
368
537
  console.log(colors.dim + `Happy vibe coding!
369
538
  ` + colors.reset);
370
539
  process.exit(0);
371
540
  } else {
372
- console.log(colors.red + " " + colors.reset + result.message);
373
- process.exit(1);
541
+ console.log(colors.yellow + "! " + colors.reset + result.message);
542
+ process.exit(0);
374
543
  }
375
544
  }
376
- function runScan(dir) {
377
- printBanner();
378
- console.log(colors.yellow + `Scanning ${dir}...
545
+ function runScan(dir, jsonOutput) {
546
+ if (!jsonOutput) {
547
+ printBanner();
548
+ console.log(colors.yellow + `Scanning ${dir}...
379
549
  ` + colors.reset);
380
- const issues = scanFiles(dir);
550
+ }
551
+ const { issues, warnings } = scanFiles(dir);
552
+ if (jsonOutput) {
553
+ console.log(formatJson(issues, warnings));
554
+ process.exit(issues.length > 0 ? 1 : 0);
555
+ }
556
+ if (warnings.length > 0) {
557
+ console.log(colors.yellow + "Warnings:" + colors.reset);
558
+ for (const warning of warnings) {
559
+ console.log(colors.yellow + " ⚠ " + colors.reset + warning);
560
+ }
561
+ console.log("");
562
+ }
381
563
  if (issues.length === 0) {
382
564
  console.log(colors.green + colors.bold + "✓ SAFE" + colors.reset);
383
565
  console.log(colors.dim + `No security issues detected. Ship it!
384
566
  ` + colors.reset);
385
567
  process.exit(0);
386
568
  }
387
- const summary = generateSummary(issues);
569
+ const severitySummary = generateSeveritySummary(issues);
388
570
  console.log(colors.red + colors.bold + `✗ Found ${issues.length} security issue${issues.length > 1 ? "s" : ""}
389
571
  ` + colors.reset);
390
- console.log("Summary:");
572
+ console.log("By severity:");
573
+ if (severitySummary.critical > 0) {
574
+ console.log(colors.red + ` • Critical: ${severitySummary.critical}` + colors.reset);
575
+ }
576
+ if (severitySummary.high > 0) {
577
+ console.log(colors.yellow + ` • High: ${severitySummary.high}` + colors.reset);
578
+ }
579
+ if (severitySummary.medium > 0) {
580
+ console.log(colors.cyan + ` • Medium: ${severitySummary.medium}` + colors.reset);
581
+ }
582
+ if (severitySummary.low > 0) {
583
+ console.log(colors.dim + ` • Low: ${severitySummary.low}` + colors.reset);
584
+ }
585
+ console.log("");
586
+ const summary = generateSummary(issues);
587
+ console.log("By type:");
391
588
  for (const [pattern, count] of Object.entries(summary)) {
392
589
  console.log(colors.dim + ` • ${pattern}: ${count}` + colors.reset);
393
590
  }
@@ -398,14 +595,16 @@ function runScan(dir) {
398
595
  process.exit(1);
399
596
  }
400
597
  var args = process.argv.slice(2);
401
- var command = args[0] || "scan";
402
- var targetDir = args[1] || process.cwd();
598
+ var jsonFlag = args.includes("--json");
599
+ var filteredArgs = args.filter((arg) => !arg.startsWith("--"));
600
+ var command = filteredArgs[0] || "scan";
601
+ var targetDir = filteredArgs[1] || process.cwd();
403
602
  switch (command) {
404
603
  case "init":
405
604
  runInit(targetDir === process.cwd() ? process.cwd() : targetDir);
406
605
  break;
407
606
  case "scan":
408
- runScan(targetDir);
607
+ runScan(targetDir, jsonFlag);
409
608
  break;
410
609
  case "help":
411
610
  case "--help":
@@ -421,7 +620,7 @@ switch (command) {
421
620
  break;
422
621
  default:
423
622
  if (command.startsWith(".") || command.startsWith("/")) {
424
- runScan(command);
623
+ runScan(command, jsonFlag);
425
624
  } else {
426
625
  console.log(colors.red + `Unknown command: ${command}` + colors.reset);
427
626
  console.log(colors.dim + `Run "vibe-shield help" for usage.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { scanFiles } from "./scanner";
2
- export { formatAgentPrompt, generateSummary } from "./prompter";
2
+ export { formatAgentPrompt, generateSummary, generateSeveritySummary, formatJson, } from "./prompter";
3
3
  export { initVibeShield } from "./init";
4
4
  export { securityPatterns } from "./patterns";
5
- export type { SecurityPattern, SecurityIssue, InitResult, IssueSummary } from "./types";
5
+ export type { SecurityPattern, SecurityIssue, InitResult, IssueSummary, ScanResult, Severity, } from "./types";
package/dist/index.js CHANGED
@@ -1,128 +1,225 @@
1
1
  // src/scanner.ts
2
- import { readFileSync, readdirSync, statSync } from "fs";
2
+ import { readFileSync, readdirSync, statSync, existsSync } from "fs";
3
3
  import { join, extname } from "path";
4
4
 
5
5
  // src/patterns.ts
6
6
  var securityPatterns = [
7
+ {
8
+ id: "aws-access-key",
9
+ name: "AWS Access Key",
10
+ regex: /['"`](AKIA[0-9A-Z]{16})['"`]/g,
11
+ fixPrompt: "AWS access key detected. Remove immediately and rotate in AWS console. Use environment variables or IAM roles.",
12
+ severity: "critical"
13
+ },
14
+ {
15
+ id: "aws-secret-key",
16
+ name: "AWS Secret Key",
17
+ regex: /aws_?secret_?access_?key\s*[:=]\s*['"`][A-Za-z0-9\/+=]{40}['"`]/gi,
18
+ fixPrompt: "AWS secret key detected. Remove immediately and rotate in AWS console. Use environment variables.",
19
+ severity: "critical"
20
+ },
21
+ {
22
+ id: "openai-key",
23
+ name: "OpenAI API Key",
24
+ regex: /['"`](sk-[A-Za-z0-9]{48,})['"`]/g,
25
+ fixPrompt: "OpenAI API key detected. Remove and rotate at platform.openai.com. Use process.env.OPENAI_API_KEY.",
26
+ severity: "critical"
27
+ },
28
+ {
29
+ id: "anthropic-key",
30
+ name: "Anthropic API Key",
31
+ regex: /['"`](sk-ant-[A-Za-z0-9\-]{80,})['"`]/g,
32
+ fixPrompt: "Anthropic API key detected. Remove and rotate at console.anthropic.com. Use process.env.ANTHROPIC_API_KEY.",
33
+ severity: "critical"
34
+ },
35
+ {
36
+ id: "stripe-secret",
37
+ name: "Stripe Secret Key",
38
+ regex: /['"`](sk_live_[A-Za-z0-9]{24,})['"`]/g,
39
+ fixPrompt: "Stripe live secret key detected. Remove and rotate in Stripe dashboard. Use process.env.STRIPE_SECRET_KEY.",
40
+ severity: "critical"
41
+ },
42
+ {
43
+ id: "stripe-restricted",
44
+ name: "Stripe Restricted Key",
45
+ regex: /['"`](rk_live_[A-Za-z0-9]{24,})['"`]/g,
46
+ fixPrompt: "Stripe restricted key detected. Remove and rotate in Stripe dashboard. Use environment variables.",
47
+ severity: "critical"
48
+ },
49
+ {
50
+ id: "github-token",
51
+ name: "GitHub Token",
52
+ regex: /['"`](ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,})['"`]/g,
53
+ fixPrompt: "GitHub token detected. Remove and rotate at github.com/settings/tokens. Use process.env.GITHUB_TOKEN.",
54
+ severity: "critical"
55
+ },
56
+ {
57
+ id: "slack-token",
58
+ name: "Slack Token",
59
+ regex: /['"`](xox[baprs]-[A-Za-z0-9\-]{10,})['"`]/g,
60
+ fixPrompt: "Slack token detected. Remove and rotate in Slack app settings. Use environment variables.",
61
+ severity: "critical"
62
+ },
63
+ {
64
+ id: "twilio-key",
65
+ name: "Twilio API Key",
66
+ regex: /['"`](SK[A-Za-z0-9]{32})['"`]/g,
67
+ fixPrompt: "Twilio API key detected. Remove and rotate in Twilio console. Use environment variables.",
68
+ severity: "critical"
69
+ },
70
+ {
71
+ id: "sendgrid-key",
72
+ name: "SendGrid API Key",
73
+ regex: /['"`](SG\.[A-Za-z0-9\-_]{22}\.[A-Za-z0-9\-_]{43})['"`]/g,
74
+ fixPrompt: "SendGrid API key detected. Remove and rotate in SendGrid dashboard. Use environment variables.",
75
+ severity: "critical"
76
+ },
77
+ {
78
+ id: "private-key",
79
+ name: "Private Key",
80
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
81
+ fixPrompt: "Private key detected in code. Move to a secure file outside repo or use a secrets manager.",
82
+ severity: "critical"
83
+ },
7
84
  {
8
85
  id: "hardcoded-secret",
9
86
  name: "Hardcoded Secret",
10
87
  regex: /(?:api_?key|api_?secret|secret_?key|auth_?token|access_?token|private_?key|client_?secret)\s*[:=]\s*['"`][A-Za-z0-9_\-\.\/\+]{8,}['"`]/gi,
11
- fixPrompt: "Move this secret to an environment variable. Add it to .env and use process.env.YOUR_SECRET. Add .env to .gitignore."
88
+ fixPrompt: "Move this secret to an environment variable. Add to .env and use process.env.YOUR_SECRET. Add .env to .gitignore.",
89
+ severity: "high"
12
90
  },
13
91
  {
14
92
  id: "hardcoded-password",
15
93
  name: "Hardcoded Password",
16
94
  regex: /(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`\s]{4,}['"`]/gi,
17
- fixPrompt: "Move this password to an environment variable. Never commit passwords to version control."
18
- },
19
- {
20
- id: "aws-key",
21
- name: "AWS Access Key",
22
- regex: /['"`](AKIA[0-9A-Z]{16})['"`]/g,
23
- fixPrompt: "AWS access key detected. Remove it immediately and rotate the key in AWS console. Use environment variables or AWS IAM roles."
95
+ fixPrompt: "Move this password to an environment variable. Never commit passwords to version control.",
96
+ severity: "high"
24
97
  },
25
98
  {
26
99
  id: "jwt-secret-inline",
27
100
  name: "Hardcoded JWT Secret",
28
101
  regex: /jwt\.sign\s*\([^)]+,\s*['"`][^'"`]{8,}['"`]/gi,
29
- fixPrompt: "Move JWT secret to an environment variable. Hardcoded secrets get committed to version control."
102
+ fixPrompt: "Move JWT secret to an environment variable. Hardcoded secrets get committed to version control.",
103
+ severity: "high"
104
+ },
105
+ {
106
+ id: "database-url",
107
+ name: "Database Connection String",
108
+ regex: /['"`](mongodb(\+srv)?|postgres(ql)?|mysql|redis):\/\/[^'"`\s]{10,}['"`]/gi,
109
+ fixPrompt: "Database connection string with credentials detected. Use process.env.DATABASE_URL.",
110
+ severity: "high"
30
111
  },
31
112
  {
32
113
  id: "sql-injection-template",
33
114
  name: "SQL Injection",
34
115
  regex: /(?:query|execute)\s*\(\s*`(?:SELECT|INSERT|UPDATE|DELETE|DROP)[^`]*\$\{/gi,
35
- fixPrompt: "Use parameterized queries instead of template literals. Example: query('SELECT * FROM users WHERE id = ?', [userId])"
116
+ fixPrompt: "Use parameterized queries instead of template literals. Example: query('SELECT * FROM users WHERE id = ?', [userId])",
117
+ severity: "high"
36
118
  },
37
119
  {
38
120
  id: "sql-injection-concat",
39
121
  name: "SQL Injection",
40
122
  regex: /(?:query|execute)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)[^'"]*['"]\s*\+/gi,
41
- fixPrompt: "Never concatenate variables into SQL strings. Use parameterized queries with placeholders."
123
+ fixPrompt: "Never concatenate variables into SQL strings. Use parameterized queries with placeholders.",
124
+ severity: "high"
42
125
  },
43
126
  {
44
127
  id: "command-injection",
45
128
  name: "Command Injection",
46
129
  regex: /(?:exec|execSync)\s*\(\s*`[^`]*\$\{/g,
47
- fixPrompt: "Avoid template literals in shell commands. Use spawn() with an array of arguments, or use a library like shell-escape."
130
+ fixPrompt: "Avoid template literals in shell commands. Use spawn() with an array of arguments.",
131
+ severity: "critical"
48
132
  },
49
133
  {
50
134
  id: "command-injection-concat",
51
135
  name: "Command Injection",
52
136
  regex: /(?:exec|execSync)\s*\([^)]*\+\s*(?:req\.|user|input|param|query|body)/gi,
53
- fixPrompt: "User input in shell commands allows arbitrary command execution. Use spawn() with argument arrays."
137
+ fixPrompt: "User input in shell commands allows arbitrary command execution. Use spawn() with argument arrays.",
138
+ severity: "critical"
54
139
  },
55
140
  {
56
141
  id: "eval-usage",
57
142
  name: "Dangerous eval()",
58
143
  regex: /[=:]\s*eval\s*\(\s*(?:req\.|user|input|param|query|body|data)/gi,
59
- fixPrompt: "eval() with user input allows arbitrary code execution. Use JSON.parse() for JSON, or refactor to avoid eval entirely."
144
+ fixPrompt: "eval() with user input allows arbitrary code execution. Use JSON.parse() for JSON, or refactor to avoid eval.",
145
+ severity: "high"
60
146
  },
61
147
  {
62
148
  id: "new-function",
63
149
  name: "Dangerous Function Constructor",
64
150
  regex: /new\s+Function\s*\([^)]*(?:req\.|user|input|param|query|body)/gi,
65
- fixPrompt: "new Function() with user input is as dangerous as eval(). Refactor to avoid dynamic code generation."
151
+ fixPrompt: "new Function() with user input is as dangerous as eval(). Refactor to avoid dynamic code generation.",
152
+ severity: "high"
66
153
  },
67
154
  {
68
155
  id: "innerhtml-variable",
69
156
  name: "XSS via innerHTML",
70
157
  regex: /\.innerHTML\s*=\s*(?:req\.|user|input|param|query|body|data|props\.|this\.)/gi,
71
- fixPrompt: "Setting innerHTML with user data enables XSS attacks. Use textContent or sanitize with DOMPurify."
158
+ fixPrompt: "Setting innerHTML with user data enables XSS attacks. Use textContent or sanitize with DOMPurify.",
159
+ severity: "high"
72
160
  },
73
161
  {
74
162
  id: "react-dangerous-html",
75
163
  name: "React XSS Risk",
76
164
  regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?:props\.|this\.|data|user|input)/gi,
77
- fixPrompt: "dangerouslySetInnerHTML with user data enables XSS. Sanitize HTML with DOMPurify first."
165
+ fixPrompt: "dangerouslySetInnerHTML with user data enables XSS. Sanitize HTML with DOMPurify first.",
166
+ severity: "high"
78
167
  },
79
168
  {
80
169
  id: "weak-hash-md5",
81
170
  name: "Weak Hash (MD5)",
82
171
  regex: /createHash\s*\(\s*['"`]md5['"`]\s*\)/g,
83
- fixPrompt: "MD5 is broken. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2."
172
+ fixPrompt: "MD5 is broken. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2.",
173
+ severity: "medium"
84
174
  },
85
175
  {
86
176
  id: "weak-hash-sha1",
87
177
  name: "Weak Hash (SHA1)",
88
178
  regex: /createHash\s*\(\s*['"`]sha1['"`]\s*\)/g,
89
- fixPrompt: "SHA1 is deprecated for security. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2."
179
+ fixPrompt: "SHA1 is deprecated for security. Use crypto.createHash('sha256'). For passwords, use bcrypt or argon2.",
180
+ severity: "medium"
90
181
  },
91
182
  {
92
183
  id: "ssl-disabled",
93
184
  name: "SSL Verification Disabled",
94
185
  regex: /rejectUnauthorized\s*:\s*false/g,
95
- fixPrompt: "Disabling SSL verification allows man-in-the-middle attacks. Remove this or set to true."
186
+ fixPrompt: "Disabling SSL verification allows man-in-the-middle attacks. Remove this or set to true.",
187
+ severity: "medium"
96
188
  },
97
189
  {
98
190
  id: "cors-wildcard",
99
191
  name: "CORS Allows All Origins",
100
192
  regex: /['"`]Access-Control-Allow-Origin['"`]\s*[,:]\s*['"`]\*['"`]/g,
101
- fixPrompt: "CORS wildcard (*) allows any website to make requests. Specify allowed origins explicitly."
193
+ fixPrompt: "CORS wildcard (*) allows any website to make requests. Specify allowed origins explicitly.",
194
+ severity: "medium"
102
195
  },
103
196
  {
104
197
  id: "path-traversal",
105
198
  name: "Path Traversal Risk",
106
199
  regex: /(?:readFile|writeFile|readFileSync|writeFileSync)\s*\(\s*(?:req\.|user|input|param|query|body)/gi,
107
- fixPrompt: "User input in file paths allows reading/writing arbitrary files. Validate paths with path.resolve() and check they're within allowed directories."
200
+ fixPrompt: "User input in file paths allows reading/writing arbitrary files. Validate paths with path.resolve() and check they're within allowed directories.",
201
+ severity: "high"
108
202
  },
109
203
  {
110
204
  id: "nosql-where",
111
205
  name: "NoSQL Injection ($where)",
112
206
  regex: /\.(find|findOne)\s*\(\s*\{[^}]*\$where\s*:/g,
113
- fixPrompt: "$where executes JavaScript and enables NoSQL injection. Use standard MongoDB query operators."
207
+ fixPrompt: "$where executes JavaScript and enables NoSQL injection. Use standard MongoDB query operators.",
208
+ severity: "high"
114
209
  },
115
210
  {
116
211
  id: "pickle-load",
117
212
  name: "Insecure Pickle (Python)",
118
213
  regex: /pickle\.loads?\s*\(\s*(?:request|user|input|data|file)/gi,
119
- fixPrompt: "pickle.load with untrusted data allows arbitrary code execution. Use JSON for untrusted data."
214
+ fixPrompt: "pickle.load with untrusted data allows arbitrary code execution. Use JSON for untrusted data.",
215
+ severity: "critical"
120
216
  },
121
217
  {
122
218
  id: "python-shell",
123
219
  name: "Shell Injection (Python)",
124
220
  regex: /subprocess\.\w+\s*\([^)]*shell\s*=\s*True[^)]*(?:request|user|input|param)/gi,
125
- fixPrompt: "shell=True with user input enables command injection. Pass command as a list without shell=True."
221
+ fixPrompt: "shell=True with user input enables command injection. Pass command as a list without shell=True.",
222
+ severity: "critical"
126
223
  }
127
224
  ];
128
225
 
@@ -184,6 +281,29 @@ function getLineNumber(content, matchIndex) {
184
281
  `);
185
282
  return lines.length;
186
283
  }
284
+ function checkGitignore(dir) {
285
+ const warnings = [];
286
+ const gitignorePath = join(dir, ".gitignore");
287
+ const envPath = join(dir, ".env");
288
+ if (!existsSync(envPath)) {
289
+ return warnings;
290
+ }
291
+ if (!existsSync(gitignorePath)) {
292
+ warnings.push(".env file exists but no .gitignore found. Create a .gitignore and add .env to prevent committing secrets.");
293
+ return warnings;
294
+ }
295
+ try {
296
+ const gitignoreContent = readFileSync(gitignorePath, "utf-8");
297
+ const lines = gitignoreContent.split(`
298
+ `).map((l) => l.trim());
299
+ const envIgnored = lines.some((line) => line === ".env" || line === ".env*" || line === "*.env" || line === ".env.local" || line.startsWith(".env"));
300
+ if (!envIgnored) {
301
+ warnings.push(".env file exists but is not in .gitignore. Add .env to .gitignore to prevent committing secrets.");
302
+ }
303
+ } catch {
304
+ }
305
+ return warnings;
306
+ }
187
307
  function scanFile(filePath) {
188
308
  const issues = [];
189
309
  let content;
@@ -204,7 +324,8 @@ function scanFile(filePath) {
204
324
  patternId: pattern.id,
205
325
  patternName: pattern.name,
206
326
  fixPrompt: pattern.fixPrompt,
207
- match: matchText.length > 60 ? matchText.substring(0, 60) + "..." : matchText
327
+ match: matchText.length > 60 ? matchText.substring(0, 60) + "..." : matchText,
328
+ severity: pattern.severity
208
329
  });
209
330
  }
210
331
  }
@@ -213,14 +334,24 @@ function scanFile(filePath) {
213
334
  function scanFiles(dir) {
214
335
  const files = collectFiles(dir);
215
336
  const allIssues = [];
337
+ const warnings = checkGitignore(dir);
216
338
  for (const file of files) {
217
339
  const issues = scanFile(file);
218
340
  allIssues.push(...issues);
219
341
  }
220
- return allIssues;
342
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
343
+ allIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
344
+ return { issues: allIssues, warnings };
221
345
  }
222
346
  // src/prompter.ts
223
- function formatAgentPrompt(issues) {
347
+ var severityColors = {
348
+ critical: "\x1B[31m",
349
+ high: "\x1B[33m",
350
+ medium: "\x1B[36m",
351
+ low: "\x1B[37m"
352
+ };
353
+ var reset = "\x1B[0m";
354
+ function formatAgentPrompt(issues, useColors = true) {
224
355
  if (!issues || issues.length === 0) {
225
356
  return "";
226
357
  }
@@ -231,7 +362,10 @@ function formatAgentPrompt(issues) {
231
362
  ""
232
363
  ];
233
364
  issues.forEach((issue, index) => {
234
- lines.push(`[TASK ${index + 1}] Fix ${issue.patternName} in ${issue.file} at line ${issue.line}`);
365
+ const severityLabel = issue.severity.toUpperCase();
366
+ const color = useColors ? severityColors[issue.severity] : "";
367
+ const colorReset = useColors ? reset : "";
368
+ lines.push(`${color}[${severityLabel}]${colorReset} [TASK ${index + 1}] Fix ${issue.patternName} in ${issue.file} at line ${issue.line}`);
235
369
  lines.push(`[FOUND]: ${issue.match}`);
236
370
  lines.push(`[INSTRUCTION]: ${issue.fixPrompt}`);
237
371
  lines.push("");
@@ -252,8 +386,38 @@ function generateSummary(issues) {
252
386
  }
253
387
  return summary;
254
388
  }
389
+ function generateSeveritySummary(issues) {
390
+ const summary = {
391
+ critical: 0,
392
+ high: 0,
393
+ medium: 0,
394
+ low: 0
395
+ };
396
+ for (const issue of issues) {
397
+ summary[issue.severity]++;
398
+ }
399
+ return summary;
400
+ }
401
+ function formatJson(issues, warnings) {
402
+ return JSON.stringify({
403
+ summary: {
404
+ total: issues.length,
405
+ bySeverity: generateSeveritySummary(issues),
406
+ byType: generateSummary(issues)
407
+ },
408
+ issues: issues.map((issue) => ({
409
+ severity: issue.severity,
410
+ type: issue.patternName,
411
+ file: issue.file,
412
+ line: issue.line,
413
+ match: issue.match,
414
+ fix: issue.fixPrompt
415
+ })),
416
+ warnings
417
+ }, null, 2);
418
+ }
255
419
  // src/init.ts
256
- import { writeFileSync, existsSync, readFileSync as readFileSync2 } from "fs";
420
+ import { writeFileSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
257
421
  import { join as join2 } from "path";
258
422
  var CURSORRULES_CONTENT = `# Vibe Shield Security Rules
259
423
 
@@ -268,7 +432,7 @@ Before marking a task as done, run \`npx vibe-shield\`. If issues are found, fol
268
432
  `;
269
433
  function initVibeShield(dir) {
270
434
  const cursorrulesPath = join2(dir, ".cursorrules");
271
- if (existsSync(cursorrulesPath)) {
435
+ if (existsSync2(cursorrulesPath)) {
272
436
  try {
273
437
  const existingContent = readFileSync2(cursorrulesPath, "utf-8");
274
438
  if (existingContent.includes("vibe-shield") || existingContent.includes("Vibe Shield")) {
@@ -315,5 +479,7 @@ export {
315
479
  scanFiles,
316
480
  initVibeShield,
317
481
  generateSummary,
482
+ generateSeveritySummary,
483
+ formatJson,
318
484
  formatAgentPrompt
319
485
  };
@@ -1,9 +1,17 @@
1
- import type { SecurityIssue, IssueSummary } from "./types";
1
+ import type { SecurityIssue, IssueSummary, Severity } from "./types";
2
2
  /**
3
3
  * Format issues into an "Agent Protocol" string that AI agents can read and act on.
4
4
  */
5
- export declare function formatAgentPrompt(issues: SecurityIssue[]): string;
5
+ export declare function formatAgentPrompt(issues: SecurityIssue[], useColors?: boolean): string;
6
6
  /**
7
7
  * Generate a summary of issues by type.
8
8
  */
9
9
  export declare function generateSummary(issues: SecurityIssue[]): IssueSummary;
10
+ /**
11
+ * Generate severity summary
12
+ */
13
+ export declare function generateSeveritySummary(issues: SecurityIssue[]): Record<Severity, number>;
14
+ /**
15
+ * Format issues as JSON for CI/CD pipelines
16
+ */
17
+ export declare function formatJson(issues: SecurityIssue[], warnings: string[]): string;
package/dist/scanner.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { SecurityIssue } from "./types";
1
+ import type { ScanResult } from "./types";
2
2
  /**
3
3
  * Scan all files in a directory for security issues.
4
4
  */
5
- export declare function scanFiles(dir: string): SecurityIssue[];
5
+ export declare function scanFiles(dir: string): ScanResult;
package/dist/types.d.ts CHANGED
@@ -1,8 +1,10 @@
1
+ export type Severity = "critical" | "high" | "medium" | "low";
1
2
  export interface SecurityPattern {
2
3
  id: string;
3
4
  name: string;
4
5
  regex: RegExp;
5
6
  fixPrompt: string;
7
+ severity: Severity;
6
8
  }
7
9
  export interface SecurityIssue {
8
10
  file: string;
@@ -11,6 +13,7 @@ export interface SecurityIssue {
11
13
  patternName: string;
12
14
  fixPrompt: string;
13
15
  match: string;
16
+ severity: Severity;
14
17
  }
15
18
  export interface InitResult {
16
19
  success: boolean;
@@ -20,3 +23,7 @@ export interface InitResult {
20
23
  export interface IssueSummary {
21
24
  [patternName: string]: number;
22
25
  }
26
+ export interface ScanResult {
27
+ issues: SecurityIssue[];
28
+ warnings: string[];
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-shield",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Security scanner for vibe coders - find and fix AI-generated security issues",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",