vibe-secure 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHECKLIST.md +12 -0
- package/README.md +80 -0
- package/index.js +88 -0
- package/package.json +27 -0
- package/src/checks/dbCheck.js +30 -0
- package/src/checks/endpointCheck.js +41 -0
- package/src/checks/secrets.js +39 -0
- package/src/checks/sqlInjection.js +48 -0
- package/src/checks/unsafeReact.js +49 -0
- package/src/scanner.js +26 -0
package/CHECKLIST.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Top 10 Dangerous Things to Check For
|
|
2
|
+
|
|
3
|
+
1. **API keys in code** (like `const key = "sk_live_123"`)
|
|
4
|
+
2. **Passwords hardcoded**
|
|
5
|
+
3. **Database URLs exposed**
|
|
6
|
+
4. **Unsafe API endpoints** (no authentication)
|
|
7
|
+
5. **Missing input validation**
|
|
8
|
+
6. **SQL injection risks**
|
|
9
|
+
7. **Suspicious npm packages**
|
|
10
|
+
8. **Missing error handling**
|
|
11
|
+
9. **Exposed environment variables**
|
|
12
|
+
10. **Insecure file uploads**
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Vibe Secure
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
> The security scanner for AI-generated applications.
|
|
5
|
+
|
|
6
|
+
Vibe Secure is a lightweight, CLI-based security scanner designed for the "vibe coding" era. It catches common mistakes that AI coding assistants (like ChatGPT, Claude, or Cursor) might accidentally leave behind in your React, Node.js, and Next.js applications.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
Features
|
|
11
|
+
--------
|
|
12
|
+
|
|
13
|
+
- Zero Config: Just run it. No complex setup files.
|
|
14
|
+
- Security Checks: Scans for common errors:
|
|
15
|
+
- Hardcoded Secrets: API keys (Stripe, AWS, OpenAI, etc.).
|
|
16
|
+
- Exposed Databases: Postgres, MongoDB, Redis connection strings.
|
|
17
|
+
- Unsafe Endpoints: Risky API routes (POST/DELETE) lacking authentication middleware.
|
|
18
|
+
- SQL Injection: Detects unsafe string concatenation in SQL queries.
|
|
19
|
+
- XSS Vulnerabilities: Detects dangerous React patterns.
|
|
20
|
+
- Clean Output: Readable CLI reports with clickable file links.
|
|
21
|
+
|
|
22
|
+
How it Works
|
|
23
|
+
------------
|
|
24
|
+
|
|
25
|
+
Vibe Secure is a static analysis tool, not an AI model.
|
|
26
|
+
|
|
27
|
+
1. Fast and Privacy-First: It runs entirely on your machine. No code leaves your computer.
|
|
28
|
+
2. Pattern Matching: It uses Regular Expressions (Regex) and heuristic rules to find patterns that look dangerous.
|
|
29
|
+
3. Deterministic: Unlike AI models which can hallucinate, this tool uses rigid pattern matching to identify specific security violations.
|
|
30
|
+
|
|
31
|
+
Usage
|
|
32
|
+
-----
|
|
33
|
+
|
|
34
|
+
You can run Vibe Secure directly in your project using npx:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx vibe-secure
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or install it globally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g vibe-secure
|
|
44
|
+
vibe-secure
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Run on a specific folder:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
vibe-secure ./src/api
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
What it Checks
|
|
54
|
+
--------------
|
|
55
|
+
|
|
56
|
+
| Check Type | Description |
|
|
57
|
+
| :--- | :--- |
|
|
58
|
+
| Secrets | Scans for sk_live_, AWS_ACCESS_KEY, and other credential patterns. |
|
|
59
|
+
| Databases | Detects hardcoded connection strings like postgres:// or mongodb+srv://. |
|
|
60
|
+
| Auth | Flags API routes like app.delete('/user/:id') that appear to have no middleware. |
|
|
61
|
+
| SQL Injection | Flags SELECT statements using string concatenation or template literals. |
|
|
62
|
+
| XSS | Flags dangerouslySetInnerHTML, javascript: links, and eval(). |
|
|
63
|
+
|
|
64
|
+
Development
|
|
65
|
+
-----------
|
|
66
|
+
|
|
67
|
+
1. Clone the repo
|
|
68
|
+
2. Install dependencies:
|
|
69
|
+
```bash
|
|
70
|
+
npm install
|
|
71
|
+
```
|
|
72
|
+
3. Run the scanner on itself:
|
|
73
|
+
```bash
|
|
74
|
+
node index.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
License
|
|
78
|
+
-------
|
|
79
|
+
|
|
80
|
+
MIT.
|
package/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const figlet = require('figlet');
|
|
6
|
+
const { scanFolder } = require('./src/scanner');
|
|
7
|
+
const { checkForSecrets } = require('./src/checks/secrets');
|
|
8
|
+
const { checkForDatabaseUrls } = require('./src/checks/dbCheck');
|
|
9
|
+
const { checkForUnsafeEndpoints } = require('./src/checks/endpointCheck');
|
|
10
|
+
const { checkForSqlInjection } = require('./src/checks/sqlInjection.js');
|
|
11
|
+
const { checkForDangerousReact } = require('./src/checks/unsafeReact.js');
|
|
12
|
+
const fs = require('fs').promises;
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
console.log(chalk.cyan(figlet.textSync('Vibe Secure', { horizontalLayout: 'full' })));
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.version('1.0.0')
|
|
21
|
+
.description('Security scanner for vibe-coded AI applications')
|
|
22
|
+
.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
|
+
|
|
27
|
+
try {
|
|
28
|
+
const files = await scanFolder(targetDir);
|
|
29
|
+
|
|
30
|
+
if (files.length === 0) {
|
|
31
|
+
console.log(chalk.yellow('⚠️ No relevant implementation files found (.js, .jsx, .ts, .tsx).'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(chalk.gray(`Found ${files.length} files to check...`));
|
|
36
|
+
|
|
37
|
+
let allIssues = [];
|
|
38
|
+
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
// Skip scanning the checker files themselves to avoid false positives during dev
|
|
41
|
+
if (file.includes('src/checks/')) continue;
|
|
42
|
+
|
|
43
|
+
const content = await fs.readFile(file, 'utf8');
|
|
44
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
45
|
+
|
|
46
|
+
const issues = [
|
|
47
|
+
...checkForSecrets(content, relativePath),
|
|
48
|
+
...checkForDatabaseUrls(content, relativePath),
|
|
49
|
+
...checkForUnsafeEndpoints(content, relativePath),
|
|
50
|
+
...checkForSqlInjection(content, relativePath),
|
|
51
|
+
...checkForDangerousReact(content, relativePath)
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
allIssues = [...allIssues, ...issues];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(chalk.gray('────────────────────────────────────────'));
|
|
58
|
+
|
|
59
|
+
if (allIssues.length > 0) {
|
|
60
|
+
console.log(chalk.red(`\n❌ Scan failed! Found ${allIssues.length} issues:\n`));
|
|
61
|
+
|
|
62
|
+
allIssues.forEach(issue => {
|
|
63
|
+
const color = issue.type === 'Critical' ? chalk.red.bold : chalk.yellow.bold;
|
|
64
|
+
|
|
65
|
+
// Standard terminal format: /absolute/path/to/file:line:col
|
|
66
|
+
// VS Code and other terminals auto-link this pattern to open the file at the line
|
|
67
|
+
const absolutePath = path.resolve(process.cwd(), issue.file);
|
|
68
|
+
|
|
69
|
+
console.log(`${color(`[${issue.type}]`)} ${chalk.underline(`${absolutePath}:${issue.line}:1`)}`);
|
|
70
|
+
console.log(` ${chalk.yellow(issue.message)}`);
|
|
71
|
+
console.log(` ${chalk.gray('>')} ${chalk.cyan(issue.code.substring(0, 60))}${issue.code.length > 60 ? '...' : ''}\n`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(chalk.red(`\n💥 Fix these ${allIssues.length} issues before shipping!`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(chalk.green('\n✨ Vibes are pristine! No obvious security issues found.'));
|
|
78
|
+
console.log(chalk.green('🚀 Ready to ship!'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(chalk.red('\n❌ Error scanning folder:'), error.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
program.parse(process.argv);
|
|
88
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibe-secure",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The security scanner for AI-generated applications code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"vibe-secure": "index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"security",
|
|
14
|
+
"scanner",
|
|
15
|
+
"ai",
|
|
16
|
+
"vibe-coding",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"author": "Vibe Secure Team",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"commander": "^14.0.2",
|
|
25
|
+
"figlet": "^1.10.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for exposed database URLs
|
|
3
|
+
* @param {string} content - File content
|
|
4
|
+
* @param {string} filePath - Path to file
|
|
5
|
+
* @returns {Array} - Array of issues found
|
|
6
|
+
*/
|
|
7
|
+
function checkForDatabaseUrls(content, filePath) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
// postgres://user:password@localhost:5432/mydb
|
|
12
|
+
// mongodb+srv://user:password@cluster.mongodb.net
|
|
13
|
+
const dbPattern = /((postgres|mysql|mongodb|redis)(\+[a-z]+)?):\/\/[a-zA-Z0-9_]+:[^@]+@/i;
|
|
14
|
+
|
|
15
|
+
lines.forEach((line, index) => {
|
|
16
|
+
if (dbPattern.test(line) && !line.includes('process.env')) {
|
|
17
|
+
issues.push({
|
|
18
|
+
file: filePath,
|
|
19
|
+
line: index + 1,
|
|
20
|
+
type: 'Critical',
|
|
21
|
+
message: 'Exposed Database Connection String',
|
|
22
|
+
code: line.trim()
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return issues;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { checkForDatabaseUrls };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for potentially unsafe API endpoints (no auth)
|
|
3
|
+
* @param {string} content - File content
|
|
4
|
+
* @param {string} filePath - Path to file
|
|
5
|
+
* @returns {Array} - Array of issues found
|
|
6
|
+
*/
|
|
7
|
+
function checkForUnsafeEndpoints(content, filePath) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
// Look for Express/Next.js route definitions
|
|
12
|
+
// app.get('/api/users', (req, res) => ...)
|
|
13
|
+
// export default function handler(req, res) ...
|
|
14
|
+
|
|
15
|
+
// This is a naive check: looks for route definitions that DON'T look like they use middleware
|
|
16
|
+
// Or simply flagging all routes for manual review (which might be too noisy)
|
|
17
|
+
|
|
18
|
+
// Better approach for "vibe coded" apps: Look for 'delete' or 'update' routes without some 'auth' keyword nearby
|
|
19
|
+
|
|
20
|
+
lines.forEach((line, index) => {
|
|
21
|
+
// Check for dangerous operations
|
|
22
|
+
if ((line.includes('app.delete') || line.includes('app.post') || line.includes('app.put')) &&
|
|
23
|
+
!line.includes('auth') &&
|
|
24
|
+
!line.includes('middleware') &&
|
|
25
|
+
!line.includes('verify')) {
|
|
26
|
+
|
|
27
|
+
// Look ahead a few lines for auth checks? (Start simple)
|
|
28
|
+
issues.push({
|
|
29
|
+
file: filePath,
|
|
30
|
+
line: index + 1,
|
|
31
|
+
type: 'Warning',
|
|
32
|
+
message: 'Potential Unsafe Endpoint (Mutation without Auth)',
|
|
33
|
+
code: line.trim()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return issues;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { checkForUnsafeEndpoints };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for common hardcoded secrets and patterns
|
|
3
|
+
* @param {string} content - File content
|
|
4
|
+
* @param {string} filePath - Path to file
|
|
5
|
+
* @returns {Array} - Array of issues found
|
|
6
|
+
*/
|
|
7
|
+
function checkForSecrets(content, filePath) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
const patterns = [
|
|
12
|
+
{ name: 'Stripe Secret Key', regex: /sk_live_[a-zA-Z0-9]{24}/ },
|
|
13
|
+
{ name: 'Generic API Key', regex: /api[_-]?key\s*[:=]\s*['"][a-zA-Z0-9_\-]{20,}['"]/i },
|
|
14
|
+
{ name: 'Hardcoded Password', regex: /password\s*[:=]\s*['"][^'"]{6,}['"]/i },
|
|
15
|
+
{ name: 'AWS Access Key', regex: /AKI[A-Z0-9]{17}/ },
|
|
16
|
+
{ name: 'Private Key Block', regex: /-----BEGIN PRIVATE KEY-----/ }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
lines.forEach((line, index) => {
|
|
20
|
+
// Basic check for very long strings that look like keys (entropy check heuristic simplified)
|
|
21
|
+
// Only check lines that look like assignments
|
|
22
|
+
|
|
23
|
+
for (const pattern of patterns) {
|
|
24
|
+
if (pattern.regex.test(line)) {
|
|
25
|
+
issues.push({
|
|
26
|
+
file: filePath,
|
|
27
|
+
line: index + 1,
|
|
28
|
+
type: 'Critical',
|
|
29
|
+
message: `Possible ${pattern.name} found`,
|
|
30
|
+
code: line.trim()
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return issues;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { checkForSecrets };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for potential SQL injection vulnerabilities
|
|
3
|
+
* @param {string} content - File content
|
|
4
|
+
* @param {string} filePath - Path to file
|
|
5
|
+
* @returns {Array} - Array of issues found
|
|
6
|
+
*/
|
|
7
|
+
function checkForSqlInjection(content, filePath) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
// Regex to look for:
|
|
12
|
+
// 1. Strings starting with SELECT/INSERT/UPDATE/DELETE (case insensitive)
|
|
13
|
+
// 2. That contain variable interpolation ${...} or string concatenation ' + '
|
|
14
|
+
|
|
15
|
+
// Heuristic:
|
|
16
|
+
// const query = `SELECT * FROM users WHERE id = ${id}`; <-- BAD
|
|
17
|
+
// db.query('SELECT * FROM users WHERE id = ' + id); <-- BAD
|
|
18
|
+
|
|
19
|
+
lines.forEach((line, index) => {
|
|
20
|
+
// Check for template literal interpolation in SQL strings
|
|
21
|
+
if (/\`\s*(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.*?\$\{.*?\}/i.test(line)) {
|
|
22
|
+
issues.push({
|
|
23
|
+
file: filePath,
|
|
24
|
+
line: index + 1,
|
|
25
|
+
type: 'Critical',
|
|
26
|
+
message: 'Potential SQL Injection (Template Literal)',
|
|
27
|
+
code: line.trim()
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for string concatenation in SQL strings (simple check)
|
|
32
|
+
// Matches: 'SELECT ... ' + variable
|
|
33
|
+
if (/['"]\s*(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.*?['"]\s*\+/i.test(line) ||
|
|
34
|
+
/\+\s*['"]\s*(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s+.*?['"]/i.test(line)) {
|
|
35
|
+
issues.push({
|
|
36
|
+
file: filePath,
|
|
37
|
+
line: index + 1,
|
|
38
|
+
type: 'Critical',
|
|
39
|
+
message: 'Potential SQL Injection (String Concatenation)',
|
|
40
|
+
code: line.trim()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return issues;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { checkForSqlInjection };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks for dangerous React patterns (XSS risks)
|
|
3
|
+
* @param {string} content - File content
|
|
4
|
+
* @param {string} filePath - Path to file
|
|
5
|
+
* @returns {Array} - Array of issues found
|
|
6
|
+
*/
|
|
7
|
+
function checkForDangerousReact(content, filePath) {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
lines.forEach((line, index) => {
|
|
12
|
+
// 1. dangerouslySetInnerHTML
|
|
13
|
+
if (line.includes('dangerouslySetInnerHTML')) {
|
|
14
|
+
issues.push({
|
|
15
|
+
file: filePath,
|
|
16
|
+
line: index + 1,
|
|
17
|
+
type: 'Warning',
|
|
18
|
+
message: 'Dangerous React Pattern (XSS Risk)',
|
|
19
|
+
code: line.trim()
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. javascript: URIs in href or src
|
|
24
|
+
if (/['"]javascript:/i.test(line)) {
|
|
25
|
+
issues.push({
|
|
26
|
+
file: filePath,
|
|
27
|
+
line: index + 1,
|
|
28
|
+
type: 'Critical',
|
|
29
|
+
message: 'Inline JavaScript URI (XSS Risk)',
|
|
30
|
+
code: line.trim()
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 3. eval()
|
|
35
|
+
if (/\beval\s*\(/.test(line)) {
|
|
36
|
+
issues.push({
|
|
37
|
+
file: filePath,
|
|
38
|
+
line: index + 1,
|
|
39
|
+
type: 'Critical',
|
|
40
|
+
message: 'Use of eval() detected',
|
|
41
|
+
code: line.trim()
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return issues;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { checkForDangerousReact };
|
package/src/scanner.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
async function scanFolder(dir, fileList = []) {
|
|
5
|
+
const files = await fs.readdir(dir);
|
|
6
|
+
|
|
7
|
+
for (const file of files) {
|
|
8
|
+
const filePath = path.join(dir, file);
|
|
9
|
+
const stat = await fs.stat(filePath);
|
|
10
|
+
|
|
11
|
+
if (stat.isDirectory()) {
|
|
12
|
+
if (file !== 'node_modules' && file !== '.git') {
|
|
13
|
+
await scanFolder(filePath, fileList);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
const ext = path.extname(file).toLowerCase();
|
|
17
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
|
|
18
|
+
fileList.push(filePath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return fileList;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { scanFolder };
|