node-protect 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/README.md +121 -0
- package/dist/cli.js +40 -0
- package/dist/index.js +72 -0
- package/dist/scanners/codeScanner.js +75 -0
- package/dist/scanners/dependencyAuditor.js +57 -0
- package/dist/scanners/secretScanner.js +53 -0
- package/dist/types.js +2 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Node-Protect 🛡️
|
|
2
|
+
|
|
3
|
+
A lightweight, zero-config security scanner for Node.js applications.
|
|
4
|
+
Detects vulnerabilities from the **OWASP Top 10** without blocking your workflow.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 🚀 Key Features
|
|
10
|
+
|
|
11
|
+
- **Non-blocking**: Runs in the background and warns you about issues. It **never crashes your app**.
|
|
12
|
+
- **Zero Config**: Works out of the box. Just install and run.
|
|
13
|
+
- **Comprehensive Coverage**: Checks for issues across the OWASP Top 10 (2021).
|
|
14
|
+
|
|
15
|
+
### What it Checks
|
|
16
|
+
|
|
17
|
+
| Category | Description |
|
|
18
|
+
| :--- | :--- |
|
|
19
|
+
| **A01 Broken Access Control** | Permissive CORS, hardcoded role checks |
|
|
20
|
+
| **A02 Cryptographic Failures** | Weak hashing (MD5/SHA1), hardcoded IVs |
|
|
21
|
+
| **A03 Injection** | `eval()`, `innerHTML`, unsafe SQL interpolation |
|
|
22
|
+
| **A04 Insecure Design** | Leaky headers (`X-Powered-By`) |
|
|
23
|
+
| **A05 Misconfiguration** | Debug mode on, hardcoded ports |
|
|
24
|
+
| **A06 Vulnerable Components** | Wraps `npm audit` to check dependencies |
|
|
25
|
+
| **A07 Authentication Failures** | **Hardcoded Secrets** (AWS keys, API tokens, passwords) |
|
|
26
|
+
| **A08 Integrity Failures** | Missing SRI, integrity checks |
|
|
27
|
+
| **A09 Logging Failures** | `console.log` usage, empty catch blocks |
|
|
28
|
+
| **A10 SSRF** | Unsafe data fetching in axios/fetch |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 📦 Installation
|
|
33
|
+
|
|
34
|
+
Install as a development dependency:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install --save-dev node-protect
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 💻 Usage
|
|
43
|
+
|
|
44
|
+
### 1. As a CLI Tool
|
|
45
|
+
|
|
46
|
+
Great for CI/CD pipelines or local checks.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Scan current directory
|
|
50
|
+
npx protect scan .
|
|
51
|
+
|
|
52
|
+
# Scan specific folder
|
|
53
|
+
npx protect scan ./src
|
|
54
|
+
|
|
55
|
+
# Scan only for secrets and code issues (skip dependencies)
|
|
56
|
+
npx protect scan . --type=secrets,code
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. As a Library (Programmatic)
|
|
60
|
+
|
|
61
|
+
Perfect for adding a security check to your server startup sequence. It runs asynchronously ("fire-and-forget").
|
|
62
|
+
|
|
63
|
+
**Example: Express Server Integration**
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
/* index.js */
|
|
67
|
+
const http = require('http');
|
|
68
|
+
const { protect } = require('node-protect');
|
|
69
|
+
|
|
70
|
+
console.log('--- Server Startup ---');
|
|
71
|
+
|
|
72
|
+
// 1. Run security scan in background
|
|
73
|
+
// It will log warnings if found, but won't stop the server
|
|
74
|
+
protect();
|
|
75
|
+
|
|
76
|
+
// 2. Start your server immediately
|
|
77
|
+
http.createServer((req, res) => {
|
|
78
|
+
res.writeHead(200);
|
|
79
|
+
res.end('Hello Secure World!');
|
|
80
|
+
}).listen(3000, () => {
|
|
81
|
+
console.log('Server running on port 3000');
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Custom Handling**
|
|
86
|
+
|
|
87
|
+
If you want to wait for results or handle them manually:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const { protect, printReport } = require('node-protect');
|
|
91
|
+
|
|
92
|
+
// Await the results
|
|
93
|
+
protect(process.cwd(), { types: ['full'], log: false }).then(results => {
|
|
94
|
+
if (results.length > 0) {
|
|
95
|
+
console.error(`🚨 Found ${results.length} vulnerabilities!`);
|
|
96
|
+
printReport(results); // Pretty print to console
|
|
97
|
+
// process.exit(1); // Optional: Exit if you want to block
|
|
98
|
+
} else {
|
|
99
|
+
console.log('✅ App is secure.');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🛠️ Configuration
|
|
107
|
+
|
|
108
|
+
The `protect()` function accepts an options object:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface ScanOptions {
|
|
112
|
+
log?: boolean; // Default: true (Auto-print warnings to console)
|
|
113
|
+
types?: string[]; // Default: ['full']. Options: 'secrets', 'code', 'dependencies'
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 📝 License
|
|
120
|
+
|
|
121
|
+
ISC
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const index_1 = require("./index");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('node-protect')
|
|
14
|
+
.description('CLI to check for project security risks (OWASP Top 10)')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
program.command('scan')
|
|
17
|
+
.argument('[path]', 'Path to project to scan', '.')
|
|
18
|
+
.option('-t, --type <type>', 'Type of scan: full, secrets, dependencies, code', 'full')
|
|
19
|
+
.action(async (projectPath, options) => {
|
|
20
|
+
const resolvedPath = path_1.default.resolve(projectPath);
|
|
21
|
+
console.log(chalk_1.default.blue(`Scanning project at: ${resolvedPath}`));
|
|
22
|
+
console.log(chalk_1.default.blue(`Scan type: ${options.type}`));
|
|
23
|
+
const types = options.type.split(',').map((t) => t.trim());
|
|
24
|
+
try {
|
|
25
|
+
// CLI handles its own printing/exit logic, so we disable internal logging
|
|
26
|
+
const results = await (0, index_1.protect)(resolvedPath, { types, log: false });
|
|
27
|
+
// Use the shared printReport function
|
|
28
|
+
const { printReport } = require('./index');
|
|
29
|
+
printReport(results);
|
|
30
|
+
if (results.length > 0) {
|
|
31
|
+
console.log(chalk_1.default.yellow('\n⚠️ Security issues found. Please review the report above.'));
|
|
32
|
+
// process.exit(1); // User requested warning only mode
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(chalk_1.default.red('Error during scan:'), error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
program.parse();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scanners = void 0;
|
|
7
|
+
exports.protect = protect;
|
|
8
|
+
exports.printReport = printReport;
|
|
9
|
+
const dependencyAuditor_1 = require("./scanners/dependencyAuditor");
|
|
10
|
+
const secretScanner_1 = require("./scanners/secretScanner");
|
|
11
|
+
const codeScanner_1 = require("./scanners/codeScanner");
|
|
12
|
+
exports.scanners = {
|
|
13
|
+
DependencyAuditor: dependencyAuditor_1.DependencyAuditor,
|
|
14
|
+
SecretScanner: secretScanner_1.SecretScanner,
|
|
15
|
+
CodeScanner: codeScanner_1.CodeScanner
|
|
16
|
+
};
|
|
17
|
+
async function protect(projectPath = process.cwd(), options = {}) {
|
|
18
|
+
const { log = true, types = ['full'] } = options;
|
|
19
|
+
try {
|
|
20
|
+
const results = [];
|
|
21
|
+
const runAll = types.includes('full');
|
|
22
|
+
if (runAll || types.includes('dependencies')) {
|
|
23
|
+
const auditor = new dependencyAuditor_1.DependencyAuditor();
|
|
24
|
+
results.push(...await auditor.scan(projectPath));
|
|
25
|
+
}
|
|
26
|
+
if (runAll || types.includes('secrets')) {
|
|
27
|
+
const secretScanner = new secretScanner_1.SecretScanner();
|
|
28
|
+
results.push(...await secretScanner.scan(projectPath));
|
|
29
|
+
}
|
|
30
|
+
if (runAll || types.includes('code')) {
|
|
31
|
+
const codeScanner = new codeScanner_1.CodeScanner();
|
|
32
|
+
results.push(...await codeScanner.scan(projectPath));
|
|
33
|
+
}
|
|
34
|
+
if (log) {
|
|
35
|
+
printReport(results);
|
|
36
|
+
if (results.length > 0) {
|
|
37
|
+
console.warn(chalk_1.default.yellow('⚠️ Vulnerabilities detected. (Warning only)'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (log) {
|
|
44
|
+
console.error(chalk_1.default.red('Security scan failed:'), error);
|
|
45
|
+
}
|
|
46
|
+
// If logging is off, we rethrow so consumer can handle it.
|
|
47
|
+
// If logging is on, we suppress it to allow app flow to continue (as per request "not call then and catche")
|
|
48
|
+
if (!log)
|
|
49
|
+
throw error;
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
54
|
+
function printReport(results) {
|
|
55
|
+
if (results.length === 0) {
|
|
56
|
+
console.log(chalk_1.default.green('\n✅ No issues found!'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log(chalk_1.default.bold.red(`\n❌ Found ${results.length} issues:\n`));
|
|
60
|
+
results.forEach((result, index) => {
|
|
61
|
+
const color = result.severity === 'CRITICAL' || result.severity === 'HIGH' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
62
|
+
console.log(color(`[${index + 1}] [${result.severity}] ${result.category}`));
|
|
63
|
+
console.log(` Description: ${result.description}`);
|
|
64
|
+
if (result.file)
|
|
65
|
+
console.log(` File: ${result.file}:${result.line || 0}`);
|
|
66
|
+
if (result.snippet)
|
|
67
|
+
console.log(` Snippet: ${chalk_1.default.gray(result.snippet)}`);
|
|
68
|
+
if (result.remediation)
|
|
69
|
+
console.log(` Remediation: ${chalk_1.default.cyan(result.remediation)}`);
|
|
70
|
+
console.log('');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CodeScanner = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const glob_1 = require("glob");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
class CodeScanner {
|
|
11
|
+
patterns = [
|
|
12
|
+
// A01:2021-Broken Access Control
|
|
13
|
+
{ name: 'Permissive CORS', regex: /Access-Control-Allow-Origin\s*:\s*\*/i, category: 'A01:2021-Broken Access Control', severity: 'HIGH', remediation: 'Restrict CORS origin to specific domains.' },
|
|
14
|
+
{ name: 'Hardcoded Role Check', regex: /if\s*\(\s*user\.role\s*===\s*['"]admin['"]\s*\)/, category: 'A01:2021-Broken Access Control', severity: 'MEDIUM', remediation: 'Use a centralized permission system (RBAC/ABAC).' },
|
|
15
|
+
// A02:2021-Cryptographic Failures
|
|
16
|
+
{ name: 'Weak Hash (MD5)', regex: /createHash\(['"]md5['"]\)/, category: 'A02:2021-Cryptographic Failures', severity: 'MEDIUM', remediation: 'Use SHA-256 or stronger.' },
|
|
17
|
+
{ name: 'Weak Hash (SHA1)', regex: /createHash\(['"]sha1['"]\)/, category: 'A02:2021-Cryptographic Failures', severity: 'MEDIUM', remediation: 'Use SHA-256 or stronger.' },
|
|
18
|
+
{ name: 'Hardcoded IV', regex: /createDecipheriv\([^,]+,\s*[^,]+,\s*['"]\w+['"]\)/, category: 'A02:2021-Cryptographic Failures', severity: 'HIGH', remediation: 'Use a random IV.' },
|
|
19
|
+
// A03:2021-Injection
|
|
20
|
+
{ name: 'Eval Usage', regex: /eval\s*\(/, category: 'A03:2021-Injection', severity: 'HIGH', remediation: 'Avoid using eval().' },
|
|
21
|
+
{ name: 'InnerHTML Usage', regex: /\.innerHTML\s*=/, category: 'A03:2021-Injection', severity: 'MEDIUM', remediation: 'Use textContent or a sanitizer library.' },
|
|
22
|
+
{ name: 'Unsafe SQL Interpolation', regex: /query\s*\(\s*['"]select.*?\$\{/i, category: 'A03:2021-Injection', severity: 'HIGH', remediation: 'Use parameterized queries.' },
|
|
23
|
+
// A04:2021-Insecure Design
|
|
24
|
+
{ name: 'X-Powered-By Header', regex: /res\.setHeader\(['"]X-Powered-By['"]/, category: 'A04:2021-Insecure Design', severity: 'LOW', remediation: 'Disable X-Powered-By header to avoid leaking stack details.' },
|
|
25
|
+
{ name: 'Disable X-Powered-By (Missing)', regex: /app\.disable\(['"]x-powered-by['"]\)/, category: 'A04:2021-Insecure Design', severity: 'INFO', remediation: 'Ensure you disable x-powered-by in Express.' },
|
|
26
|
+
// A05:2021-Security Misconfiguration
|
|
27
|
+
{ name: 'Debug Mode Enabled', regex: /DEBUG\s*=\s*true/i, category: 'A05:2021-Security Misconfiguration', severity: 'MEDIUM', remediation: 'Ensure debug mode is disabled in production.' },
|
|
28
|
+
{ name: 'Hardcoded Port 80', regex: /\.listen\(\s*80\s*\)/, category: 'A05:2021-Security Misconfiguration', severity: 'LOW', remediation: 'Use environment variables for ports and avoid privileged ports.' },
|
|
29
|
+
// A08:2021-Software and Data Integrity Failures
|
|
30
|
+
// This is hard to regex in code, usually checks for lockfiles usage or SRI in HTML
|
|
31
|
+
{ name: 'Missing SRI', regex: /<script\s+src=['"]https?:\/\/[^'"]+['"](?!.*integrity)/, category: 'A08:2021-Software and Data Integrity Failures', severity: 'MEDIUM', remediation: 'Use Subresource Integrity (SRI) for external scripts.' },
|
|
32
|
+
// A09:2021-Security Logging and Monitoring Failures
|
|
33
|
+
{ name: 'Console Log Usage', regex: /console\.log\s*\(/, category: 'A09:2021-Security Logging and Monitoring Failures', severity: 'LOW', remediation: 'Use a structured logger (like winston/pino) to ensure logs are centralized.' },
|
|
34
|
+
{ name: 'Empty Catch Block', regex: /catch\s*\(\s*\w+\s*\)\s*\{\s*\}/, category: 'A09:2021-Security Logging and Monitoring Failures', severity: 'MEDIUM', remediation: 'Log all errors.' },
|
|
35
|
+
// A10:2021-Server-Side Request Forgery (SSRF)
|
|
36
|
+
{ name: 'Unsafe Fetch with Request Data', regex: /fetch\s*\(\s*req\.(query|body|params)/, category: 'A10:2021-SSRF', severity: 'HIGH', remediation: 'Validate user input before using it in fetch/axios URLs.' },
|
|
37
|
+
{ name: 'Unsafe Axios with Request Data', regex: /axios\.(get|post|put)\s*\(\s*req\.(query|body|params)/, category: 'A10:2021-SSRF', severity: 'HIGH', remediation: 'Validate user input before using it in fetch/axios URLs.' },
|
|
38
|
+
];
|
|
39
|
+
async scan(projectPath) {
|
|
40
|
+
const results = [];
|
|
41
|
+
const files = await (0, glob_1.glob)('**/*.{ts,js}', {
|
|
42
|
+
cwd: projectPath,
|
|
43
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**', '**/*.test.ts', '**/*.spec.ts'],
|
|
44
|
+
nodir: true
|
|
45
|
+
});
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const fullPath = path_1.default.join(projectPath, file);
|
|
48
|
+
try {
|
|
49
|
+
const content = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
50
|
+
const lines = content.split('\n');
|
|
51
|
+
for (let i = 0; i < lines.length; i++) {
|
|
52
|
+
const line = lines[i];
|
|
53
|
+
for (const pattern of this.patterns) {
|
|
54
|
+
if (pattern.regex.test(line)) {
|
|
55
|
+
results.push({
|
|
56
|
+
category: pattern.category,
|
|
57
|
+
severity: pattern.severity,
|
|
58
|
+
description: `Potential Security Issue: ${pattern.name}`,
|
|
59
|
+
file: file,
|
|
60
|
+
line: i + 1,
|
|
61
|
+
snippet: line.trim(),
|
|
62
|
+
remediation: pattern.remediation
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// Ignore read errors
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.CodeScanner = CodeScanner;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DependencyAuditor = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const util_1 = __importDefault(require("util"));
|
|
9
|
+
const execPromise = util_1.default.promisify(child_process_1.exec);
|
|
10
|
+
class DependencyAuditor {
|
|
11
|
+
async scan(projectPath) {
|
|
12
|
+
try {
|
|
13
|
+
// npm audit usually returns non-zero exit code if vulnerabilities are found
|
|
14
|
+
// so we catch the error to process the output
|
|
15
|
+
await execPromise('npm audit --json', { cwd: projectPath });
|
|
16
|
+
return []; // No vulnerabilities found
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
// If it's a real error (not just vulnerabilities found), rethrow
|
|
20
|
+
if (!error.stdout) {
|
|
21
|
+
console.error("Error running npm audit:", error);
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const auditReport = JSON.parse(error.stdout);
|
|
26
|
+
return this.parseAuditReport(auditReport);
|
|
27
|
+
}
|
|
28
|
+
catch (parseError) {
|
|
29
|
+
console.error("Failed to parse npm audit output", parseError);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
parseAuditReport(report) {
|
|
35
|
+
const results = [];
|
|
36
|
+
// Check for errors in report (npm audit sometimes returns error structure)
|
|
37
|
+
if (report.error) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
if (report.vulnerabilities) {
|
|
41
|
+
for (const [name, detail] of Object.entries(report.vulnerabilities)) {
|
|
42
|
+
const vulnerability = detail;
|
|
43
|
+
if (vulnerability.severity) {
|
|
44
|
+
results.push({
|
|
45
|
+
category: "A06:2021-Vulnerable and Outdated Components",
|
|
46
|
+
severity: vulnerability.severity.toUpperCase(),
|
|
47
|
+
description: `Package '${name}' has a ${vulnerability.severity} vulnerability.`,
|
|
48
|
+
file: 'package.json',
|
|
49
|
+
remediation: vulnerability.fixAvailable ? "Run 'npm audit fix'" : "Update dependency manually"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.DependencyAuditor = DependencyAuditor;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SecretScanner = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const glob_1 = require("glob");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
class SecretScanner {
|
|
11
|
+
patterns = [
|
|
12
|
+
{ name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/, severity: 'HIGH' },
|
|
13
|
+
{ name: 'Private Key', regex: /-----BEGIN PRIVATE KEY-----/, severity: 'CRITICAL' },
|
|
14
|
+
{ name: 'Generic API Key', regex: /api_key\s*[:=]\s*['"][a-zA-Z0-9_\-]{20,}['"]/, severity: 'HIGH' },
|
|
15
|
+
{ name: 'Generic Password', regex: /password\s*[:=]\s*['"][a-zA-Z0-9_\-!@#$%^&*]{6,}['"]/, severity: 'MEDIUM' },
|
|
16
|
+
];
|
|
17
|
+
async scan(projectPath) {
|
|
18
|
+
const results = [];
|
|
19
|
+
const files = await (0, glob_1.glob)('**/*.{ts,js,json,env,txt,yml,yaml}', {
|
|
20
|
+
cwd: projectPath,
|
|
21
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**'],
|
|
22
|
+
nodir: true
|
|
23
|
+
});
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const fullPath = path_1.default.join(projectPath, file);
|
|
26
|
+
try {
|
|
27
|
+
const content = await promises_1.default.readFile(fullPath, 'utf-8');
|
|
28
|
+
const lines = content.split('\n');
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
for (const pattern of this.patterns) {
|
|
32
|
+
if (pattern.regex.test(line)) {
|
|
33
|
+
results.push({
|
|
34
|
+
category: "A07:2021-Identification and Authentication Failures",
|
|
35
|
+
severity: pattern.severity,
|
|
36
|
+
description: `Potential ${pattern.name} found`,
|
|
37
|
+
file: file,
|
|
38
|
+
line: i + 1,
|
|
39
|
+
snippet: line.trim().substring(0, 100), // Truncate lengthy lines
|
|
40
|
+
remediation: "Store secrets in environment variables or a secure vault. Do not commit them to repo."
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
// Ignore read errors
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.SecretScanner = SecretScanner;
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-protect",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Security scanner for Node.js projects checking for OWASP Top 10 risks",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"protect": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"test": "jest"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"security",
|
|
20
|
+
"owasp",
|
|
21
|
+
"scanner",
|
|
22
|
+
"audit",
|
|
23
|
+
"sast"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"commander": "^14.0.2",
|
|
30
|
+
"glob": "^13.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/jest": "^30.0.0",
|
|
34
|
+
"@types/node": "^25.0.10",
|
|
35
|
+
"jest": "^30.2.0",
|
|
36
|
+
"listr2": "^10.1.0",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"typescript": "^5.9.3"
|
|
39
|
+
}
|
|
40
|
+
}
|