vibe-secure 1.0.1 → 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/README.md CHANGED
@@ -60,6 +60,29 @@ What it Checks
60
60
  | Auth | Flags API routes like app.delete('/user/:id') that appear to have no middleware. |
61
61
  | SQL Injection | Flags SELECT statements using string concatenation or template literals. |
62
62
  | XSS | Flags dangerouslySetInnerHTML, javascript: links, and eval(). |
63
+ | **AI Analysis** | (Optional) Uses **Google Gemini 2.0 Flash** to find logic flaws and subtle bugs. |
64
+
65
+ AI-Powered Analysis (Experimental)
66
+ ----------------------------------
67
+
68
+ Vibe Secure now supports **AI-powered security audits** using Google Gemini. This goes beyond regex to find logic bugs, such as missing permission checks or authentication bypasses.
69
+
70
+ **Requirements:**
71
+ 1. Get a [Google Gemini API Key](https://aistudio.google.com/).
72
+ 2. Set the environment variable:
73
+ ```bash
74
+ export GEMINI_API_KEY=your_key_here
75
+ ```
76
+
77
+ **Usage:**
78
+ Run with the `--ai` flag:
79
+
80
+ ```bash
81
+ vibe-secure . --ai
82
+ ```
83
+
84
+ > **Note:** This feature sends your code snippet (up to 10k chars) to Google's AI for analysis. Use with caution on private codebases.
85
+
63
86
 
64
87
  Development
65
88
  -----------
package/index.js CHANGED
@@ -3,12 +3,15 @@
3
3
  const { Command } = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const figlet = require('figlet');
6
- const { scanFolder } = require('./src/scanner');
6
+ const { scanFolder } = require('./src/core/scanner');
7
7
  const { checkForSecrets } = require('./src/checks/secrets');
8
+
8
9
  const { checkForDatabaseUrls } = require('./src/checks/dbCheck');
9
10
  const { checkForUnsafeEndpoints } = require('./src/checks/endpointCheck');
10
11
  const { checkForSqlInjection } = require('./src/checks/sqlInjection.js');
11
12
  const { checkForDangerousReact } = require('./src/checks/unsafeReact.js');
13
+ const { checkForEval } = require('./src/checks/ast/eval.js');
14
+ const { aiSecurityReview } = require('./src/checks/ai/analysis.js');
12
15
  const fs = require('fs').promises;
13
16
  const path = require('path');
14
17
 
@@ -17,15 +20,23 @@ const program = new Command();
17
20
  console.log(chalk.cyan(figlet.textSync('Vibe Secure', { horizontalLayout: 'full' })));
18
21
 
19
22
  program
20
- .version('1.0.0')
23
+ .version('1.0.1')
21
24
  .description('Security scanner for vibe-coded AI applications')
22
25
  .argument('[directory]', 'Directory to scan', '.')
23
- .action(async (directory) => {
24
- const targetDir = path.resolve(directory);
25
- console.log(chalk.blue(`\nšŸ” Scanning directory: ${targetDir}\n`));
26
+ .option('--ai', 'Enable AI-powered analysis (requires GEMINI_API_KEY)')
27
+ .action(async (directory, options) => {
28
+ const targetPath = path.resolve(directory);
29
+ console.log(chalk.blue(`\nšŸ” Scanning: ${targetPath}\n`));
26
30
 
27
31
  try {
28
- const files = await scanFolder(targetDir);
32
+ let files = [];
33
+ const stat = await fs.stat(targetPath);
34
+
35
+ if (stat.isFile()) {
36
+ files = [targetPath];
37
+ } else {
38
+ files = await scanFolder(targetPath);
39
+ }
29
40
 
30
41
  if (files.length === 0) {
31
42
  console.log(chalk.yellow('āš ļø No relevant implementation files found (.js, .jsx, .ts, .tsx).'));
@@ -48,10 +59,21 @@ program
48
59
  ...checkForDatabaseUrls(content, relativePath),
49
60
  ...checkForUnsafeEndpoints(content, relativePath),
50
61
  ...checkForSqlInjection(content, relativePath),
51
- ...checkForDangerousReact(content, relativePath)
62
+ ...checkForDangerousReact(content, relativePath),
63
+ ...checkForEval(content, relativePath)
52
64
  ];
53
65
 
54
66
  allIssues = [...allIssues, ...issues];
67
+
68
+ // AI Analysis (Optional)
69
+ if (options.ai) {
70
+ if (!process.env.GEMINI_API_KEY) {
71
+ console.warn(chalk.yellow(`\nāš ļø Skipping specific file AI analysis: GEMINI_API_KEY not found.`));
72
+ } else {
73
+ const aiIssues = await aiSecurityReview(content, relativePath);
74
+ allIssues.push(...aiIssues);
75
+ }
76
+ }
55
77
  }
56
78
 
57
79
  console.log(chalk.gray('────────────────────────────────────────'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-secure",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "The security scanner for AI-generated applications code",
5
5
  "bin": {
6
6
  "vibe-secure": "index.js"
@@ -20,8 +20,13 @@
20
20
  "license": "MIT",
21
21
  "type": "commonjs",
22
22
  "dependencies": {
23
+ "@google/generative-ai": "^0.24.1",
23
24
  "chalk": "^4.1.2",
24
25
  "commander": "^14.0.2",
25
- "figlet": "^1.10.0"
26
+ "dotenv": "^17.2.3",
27
+ "figlet": "^1.10.0",
28
+ "tree-sitter": "^0.21.1",
29
+ "tree-sitter-javascript": "^0.21.4",
30
+ "tree-sitter-typescript": "^0.21.2"
26
31
  }
27
32
  }
@@ -0,0 +1,70 @@
1
+ const { GoogleGenerativeAI } = require("@google/generative-ai");
2
+
3
+ /**
4
+ * Analyzes code using Google Gemini for security vulnerabilities
5
+ * @param {string} code - The code content
6
+ * @param {string} filePath - The path to the file
7
+ * @returns {Promise<Array>} - Array of found issues
8
+ */
9
+ async function aiSecurityReview(code, filePath) {
10
+ const apiKey = process.env.GEMINI_API_KEY;
11
+ if (!apiKey) {
12
+ return [];
13
+ }
14
+
15
+ const genAI = new GoogleGenerativeAI(apiKey);
16
+ // Using gemini-2.0-flash for speed and availability
17
+ const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
18
+
19
+ const prompt = `You are an expert security auditor. Analyze the following code for security vulnerabilities.
20
+ Focus on:
21
+ 1. Injection attacks (SQL, XSS, Command Injection)
22
+ 2. Authentication/Authorization bypasses
23
+ 3. Hardcoded secrets not caught by regex
24
+ 4. Exposed sensitive data
25
+ 5. Logic flaws
26
+
27
+ Return ONLY a JSON object with this valid schema:
28
+ {
29
+ "issues": [
30
+ {
31
+ "severity": "Critical" | "High" | "Medium" | "Low",
32
+ "line": <number>,
33
+ "message": "<concise description>",
34
+ "fix": "<brief fix suggestion>"
35
+ }
36
+ ]
37
+ }
38
+
39
+ If no issues are found, return { "issues": [] }.
40
+
41
+ Code to analyze (${filePath}):
42
+ ${code.substring(0, 10000)}
43
+ `; // Truncate to avoid context limit
44
+
45
+ try {
46
+ const result = await model.generateContent(prompt);
47
+ const response = await result.response;
48
+ const responseText = response.text();
49
+
50
+ // Extract JSON from response (handle potential markdown blocks)
51
+ const jsonMatch = responseText.match(/\{.*\}/s);
52
+ if (!jsonMatch) return [];
53
+
54
+ const parsed = JSON.parse(jsonMatch[0]);
55
+
56
+ return parsed.issues.map(issue => ({
57
+ file: filePath,
58
+ line: issue.line || 1, // Fallback
59
+ type: `[AI] ${issue.severity}`,
60
+ message: issue.message,
61
+ code: issue.fix || "Check logic"
62
+ }));
63
+
64
+ } catch (error) {
65
+ console.error("AI Analysis failed:", error.message);
66
+ return [];
67
+ }
68
+ }
69
+
70
+ module.exports = { aiSecurityReview };
@@ -0,0 +1,53 @@
1
+ const parser = require('../../core/parser');
2
+
3
+ /**
4
+ * Checks for usage of eval() using Tree-sitter
5
+ * @param {string} content
6
+ * @param {string} filePath
7
+ * @returns {Array} issues
8
+ */
9
+ function checkForEval(content, filePath) {
10
+ const issues = [];
11
+
12
+ try {
13
+ const tree = parser.parse(content, filePath);
14
+ const ext = filePath.split('.').pop();
15
+ const lang = ext === 'tsx' ? 'tsx' : (ext === 'ts' ? 'ts' : 'js');
16
+
17
+ // Query for function calls to 'eval'
18
+ // (call_expression function: (identifier) @name (#eq? @name "eval"))
19
+ const query = `
20
+ (call_expression
21
+ function: (identifier) @func
22
+ (#eq? @func "eval")
23
+ ) @call
24
+ `;
25
+
26
+ const matches = parser.query(tree, query, lang);
27
+
28
+ for (const match of matches) {
29
+ // match.captures[0].node is the captured node
30
+ const node = match.captures[0].node;
31
+
32
+ // Extract the code line
33
+ const lineContent = content.split('\n')[node.startPosition.row] || '';
34
+
35
+ issues.push({
36
+ file: filePath,
37
+ line: node.startPosition.row + 1,
38
+ column: node.startPosition.column,
39
+ message: 'Dangerous use of eval() detected (AST Verified)',
40
+ type: 'Critical',
41
+ code: lineContent.trim()
42
+ });
43
+ }
44
+
45
+ } catch (error) {
46
+ // Fallback or ignore parse errors
47
+ // console.error(error);
48
+ }
49
+
50
+ return issues;
51
+ }
52
+
53
+ module.exports = { checkForEval };
@@ -0,0 +1,51 @@
1
+ const Parser = require('tree-sitter');
2
+ const JavaScript = require('tree-sitter-javascript');
3
+ const TypeScript = require('tree-sitter-typescript').typescript;
4
+ const TSX = require('tree-sitter-typescript').tsx;
5
+
6
+ class CodeParser {
7
+ constructor() {
8
+ this.jsParser = new Parser();
9
+ this.jsParser.setLanguage(JavaScript);
10
+
11
+ this.tsParser = new Parser();
12
+ this.tsParser.setLanguage(TypeScript);
13
+
14
+ this.tsxParser = new Parser();
15
+ this.tsxParser.setLanguage(TSX);
16
+ }
17
+
18
+ /**
19
+ * Parse code into an AST
20
+ * @param {string} code
21
+ * @param {string} filePath
22
+ * @returns {Parser.Tree}
23
+ */
24
+ parse(code, filePath) {
25
+ if (filePath.endsWith('.tsx')) {
26
+ return this.tsxParser.parse(code);
27
+ } else if (filePath.endsWith('.ts')) {
28
+ return this.tsParser.parse(code);
29
+ } else {
30
+ return this.jsParser.parse(code);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Run a query on the code
36
+ * @param {Parser.Tree} tree
37
+ * @param {string} queryStr
38
+ * @param {string} language - 'js', 'ts', 'tsx'
39
+ * @returns {Parser.QueryCapture[]}
40
+ */
41
+ query(tree, queryStr, language = 'js') {
42
+ let langObj = JavaScript;
43
+ if (language === 'tsx') langObj = TSX;
44
+ if (language === 'ts') langObj = TypeScript;
45
+
46
+ const query = new Parser.Query(langObj, queryStr);
47
+ return query.matches(tree.rootNode);
48
+ }
49
+ }
50
+
51
+ module.exports = new CodeParser();
File without changes