vibe-shield 1.0.0 → 1.2.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/hook.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export interface HookResult {
2
+ success: boolean;
3
+ message: string;
4
+ path?: string;
5
+ }
6
+ export declare function installHook(dir: string): HookResult;
7
+ export declare function uninstallHook(dir: string): HookResult;
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
  };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};