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/README.md +51 -0
- package/dist/cli.js +14726 -255
- package/dist/hook.d.ts +7 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +198 -32
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +14049 -0
- package/dist/prompter.d.ts +10 -2
- package/dist/scanner.d.ts +2 -2
- package/dist/types.d.ts +7 -0
- package/package.json +8 -4
package/dist/hook.d.ts
ADDED
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
};
|