node-protect 1.0.0 → 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/dist/cli.js +3 -6
- package/dist/config.js +19 -0
- package/dist/index.js +9 -7
- package/dist/rules.js +162 -0
- package/dist/scanners/codeScanner.js +18 -37
- package/dist/scanners/dependencyAuditor.js +24 -9
- package/dist/scanners/secretScanner.js +18 -15
- package/package.json +15 -5
package/dist/cli.js
CHANGED
|
@@ -9,11 +9,9 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const index_1 = require("./index");
|
|
11
11
|
const program = new commander_1.Command();
|
|
12
|
+
program.name('node-protect').description('CLI to check for project security risks (OWASP Top 10)').version('1.0.0');
|
|
12
13
|
program
|
|
13
|
-
.
|
|
14
|
-
.description('CLI to check for project security risks (OWASP Top 10)')
|
|
15
|
-
.version('1.0.0');
|
|
16
|
-
program.command('scan')
|
|
14
|
+
.command('scan')
|
|
17
15
|
.argument('[path]', 'Path to project to scan', '.')
|
|
18
16
|
.option('-t, --type <type>', 'Type of scan: full, secrets, dependencies, code', 'full')
|
|
19
17
|
.action(async (projectPath, options) => {
|
|
@@ -25,8 +23,7 @@ program.command('scan')
|
|
|
25
23
|
// CLI handles its own printing/exit logic, so we disable internal logging
|
|
26
24
|
const results = await (0, index_1.protect)(resolvedPath, { types, log: false });
|
|
27
25
|
// Use the shared printReport function
|
|
28
|
-
|
|
29
|
-
printReport(results);
|
|
26
|
+
(0, index_1.printReport)(results);
|
|
30
27
|
if (results.length > 0) {
|
|
31
28
|
console.log(chalk_1.default.yellow('\n⚠️ Security issues found. Please review the report above.'));
|
|
32
29
|
// process.exit(1); // User requested warning only mode
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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.loadConfig = loadConfig;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function loadConfig(projectPath) {
|
|
10
|
+
const configPath = path_1.default.join(projectPath, '.protectrc');
|
|
11
|
+
try {
|
|
12
|
+
const content = await promises_1.default.readFile(configPath, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// Return empty config if file doesn't exist or is invalid
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,24 +12,26 @@ const codeScanner_1 = require("./scanners/codeScanner");
|
|
|
12
12
|
exports.scanners = {
|
|
13
13
|
DependencyAuditor: dependencyAuditor_1.DependencyAuditor,
|
|
14
14
|
SecretScanner: secretScanner_1.SecretScanner,
|
|
15
|
-
CodeScanner: codeScanner_1.CodeScanner
|
|
15
|
+
CodeScanner: codeScanner_1.CodeScanner,
|
|
16
16
|
};
|
|
17
|
+
const config_1 = require("./config");
|
|
17
18
|
async function protect(projectPath = process.cwd(), options = {}) {
|
|
18
19
|
const { log = true, types = ['full'] } = options;
|
|
19
20
|
try {
|
|
21
|
+
const config = await (0, config_1.loadConfig)(projectPath);
|
|
20
22
|
const results = [];
|
|
21
23
|
const runAll = types.includes('full');
|
|
22
24
|
if (runAll || types.includes('dependencies')) {
|
|
23
25
|
const auditor = new dependencyAuditor_1.DependencyAuditor();
|
|
24
|
-
results.push(...await auditor.scan(projectPath));
|
|
26
|
+
results.push(...(await auditor.scan(projectPath)));
|
|
25
27
|
}
|
|
26
28
|
if (runAll || types.includes('secrets')) {
|
|
27
|
-
const secretScanner = new secretScanner_1.SecretScanner();
|
|
28
|
-
results.push(...await secretScanner.scan(projectPath));
|
|
29
|
+
const secretScanner = new secretScanner_1.SecretScanner(config);
|
|
30
|
+
results.push(...(await secretScanner.scan(projectPath)));
|
|
29
31
|
}
|
|
30
32
|
if (runAll || types.includes('code')) {
|
|
31
|
-
const codeScanner = new codeScanner_1.CodeScanner();
|
|
32
|
-
results.push(...await codeScanner.scan(projectPath));
|
|
33
|
+
const codeScanner = new codeScanner_1.CodeScanner(config);
|
|
34
|
+
results.push(...(await codeScanner.scan(projectPath)));
|
|
33
35
|
}
|
|
34
36
|
if (log) {
|
|
35
37
|
printReport(results);
|
|
@@ -43,7 +45,7 @@ async function protect(projectPath = process.cwd(), options = {}) {
|
|
|
43
45
|
if (log) {
|
|
44
46
|
console.error(chalk_1.default.red('Security scan failed:'), error);
|
|
45
47
|
}
|
|
46
|
-
// If logging is off, we rethrow so consumer can handle it.
|
|
48
|
+
// If logging is off, we rethrow so consumer can handle it.
|
|
47
49
|
// If logging is on, we suppress it to allow app flow to continue (as per request "not call then and catche")
|
|
48
50
|
if (!log)
|
|
49
51
|
throw error;
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SECRET_RULES = exports.CODE_RULES = void 0;
|
|
4
|
+
exports.CODE_RULES = [
|
|
5
|
+
// A01:2021-Broken Access Control
|
|
6
|
+
{
|
|
7
|
+
name: 'Permissive CORS',
|
|
8
|
+
regex: /Access-Control-Allow-Origin\s*:\s*\*/i,
|
|
9
|
+
category: 'A01:2021-Broken Access Control',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
remediation: 'Restrict CORS origin to specific domains.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Hardcoded Role Check',
|
|
15
|
+
regex: /if\s*\(\s*user\.role\s*===\s*['"]admin['"]\s*\)/,
|
|
16
|
+
category: 'A01:2021-Broken Access Control',
|
|
17
|
+
severity: 'MEDIUM',
|
|
18
|
+
remediation: 'Use a centralized permission system (RBAC/ABAC).',
|
|
19
|
+
},
|
|
20
|
+
// A02:2021-Cryptographic Failures
|
|
21
|
+
{
|
|
22
|
+
name: 'Weak Hash (MD5)',
|
|
23
|
+
regex: /createHash\(['"]md5['"]\)/,
|
|
24
|
+
category: 'A02:2021-Cryptographic Failures',
|
|
25
|
+
severity: 'MEDIUM',
|
|
26
|
+
remediation: 'Use SHA-256 or stronger.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Weak Hash (SHA1)',
|
|
30
|
+
regex: /createHash\(['"]sha1['"]\)/,
|
|
31
|
+
category: 'A02:2021-Cryptographic Failures',
|
|
32
|
+
severity: 'MEDIUM',
|
|
33
|
+
remediation: 'Use SHA-256 or stronger.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Hardcoded IV',
|
|
37
|
+
regex: /createDecipheriv\([^,]+,\s*[^,]+,\s*['"]\w+['"]\)/,
|
|
38
|
+
category: 'A02:2021-Cryptographic Failures',
|
|
39
|
+
severity: 'HIGH',
|
|
40
|
+
remediation: 'Use a random IV.',
|
|
41
|
+
},
|
|
42
|
+
// A03:2021-Injection
|
|
43
|
+
{
|
|
44
|
+
name: 'Eval Usage',
|
|
45
|
+
regex: /eval\s*\(/,
|
|
46
|
+
category: 'A03:2021-Injection',
|
|
47
|
+
severity: 'HIGH',
|
|
48
|
+
remediation: 'Avoid using eval().',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'InnerHTML Usage',
|
|
52
|
+
regex: /\.innerHTML\s*=/,
|
|
53
|
+
category: 'A03:2021-Injection',
|
|
54
|
+
severity: 'MEDIUM',
|
|
55
|
+
remediation: 'Use textContent or a sanitizer library.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Unsafe SQL Interpolation',
|
|
59
|
+
regex: /query\s*\(\s*['"]select.*?\$\{/i,
|
|
60
|
+
category: 'A03:2021-Injection',
|
|
61
|
+
severity: 'HIGH',
|
|
62
|
+
remediation: 'Use parameterized queries.',
|
|
63
|
+
},
|
|
64
|
+
// A04:2021-Insecure Design
|
|
65
|
+
{
|
|
66
|
+
name: 'X-Powered-By Header',
|
|
67
|
+
regex: /res\.setHeader\(['"]X-Powered-By['"]/,
|
|
68
|
+
category: 'A04:2021-Insecure Design',
|
|
69
|
+
severity: 'LOW',
|
|
70
|
+
remediation: 'Disable X-Powered-By header to avoid leaking stack details.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'Disable X-Powered-By (Missing)',
|
|
74
|
+
regex: /app\.disable\(['"]x-powered-by['"]\)/,
|
|
75
|
+
category: 'A04:2021-Insecure Design',
|
|
76
|
+
severity: 'INFO',
|
|
77
|
+
remediation: 'Ensure you disable x-powered-by in Express.',
|
|
78
|
+
},
|
|
79
|
+
// A05:2021-Security Misconfiguration
|
|
80
|
+
{
|
|
81
|
+
name: 'Debug Mode Enabled',
|
|
82
|
+
regex: /DEBUG\s*=\s*true/i,
|
|
83
|
+
category: 'A05:2021-Security Misconfiguration',
|
|
84
|
+
severity: 'MEDIUM',
|
|
85
|
+
remediation: 'Ensure debug mode is disabled in production.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'Hardcoded Port 80',
|
|
89
|
+
regex: /\.listen\(\s*80\s*\)/,
|
|
90
|
+
category: 'A05:2021-Security Misconfiguration',
|
|
91
|
+
severity: 'LOW',
|
|
92
|
+
remediation: 'Use environment variables for ports and avoid privileged ports.',
|
|
93
|
+
},
|
|
94
|
+
// A08:2021-Software and Data Integrity Failures
|
|
95
|
+
{
|
|
96
|
+
name: 'Missing SRI',
|
|
97
|
+
regex: /<script\s+src=['"]https?:\/\/[^'"]+['"](?!.*integrity)/,
|
|
98
|
+
category: 'A08:2021-Software and Data Integrity Failures',
|
|
99
|
+
severity: 'MEDIUM',
|
|
100
|
+
remediation: 'Use Subresource Integrity (SRI) for external scripts.',
|
|
101
|
+
},
|
|
102
|
+
// A09:2021-Security Logging and Monitoring Failures
|
|
103
|
+
{
|
|
104
|
+
name: 'Console Log Usage',
|
|
105
|
+
regex: /console\.log\s*\(/,
|
|
106
|
+
category: 'A09:2021-Security Logging and Monitoring Failures',
|
|
107
|
+
severity: 'LOW',
|
|
108
|
+
remediation: 'Use a structured logger (like winston/pino) to ensure logs are centralized.',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Empty Catch Block',
|
|
112
|
+
regex: /catch\s*\(\s*\w+\s*\)\s*\{\s*\}/,
|
|
113
|
+
category: 'A09:2021-Security Logging and Monitoring Failures',
|
|
114
|
+
severity: 'MEDIUM',
|
|
115
|
+
remediation: 'Log all errors.',
|
|
116
|
+
},
|
|
117
|
+
// A10:2021-Server-Side Request Forgery (SSRF)
|
|
118
|
+
{
|
|
119
|
+
name: 'Unsafe Fetch with Request Data',
|
|
120
|
+
regex: /fetch\s*\(\s*req\.(query|body|params)/,
|
|
121
|
+
category: 'A10:2021-SSRF',
|
|
122
|
+
severity: 'HIGH',
|
|
123
|
+
remediation: 'Validate user input before using it in fetch/axios URLs.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'Unsafe Axios with Request Data',
|
|
127
|
+
regex: /axios\.(get|post|put)\s*\(\s*req\.(query|body|params)/,
|
|
128
|
+
category: 'A10:2021-SSRF',
|
|
129
|
+
severity: 'HIGH',
|
|
130
|
+
remediation: 'Validate user input before using it in fetch/axios URLs.',
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
exports.SECRET_RULES = [
|
|
134
|
+
{
|
|
135
|
+
name: 'AWS Access Key',
|
|
136
|
+
regex: /AKIA[0-9A-Z]{16}/,
|
|
137
|
+
category: 'A07:2021-Identification and Authentication Failures',
|
|
138
|
+
severity: 'HIGH',
|
|
139
|
+
remediation: 'Store secrets in environment variables or a secure vault. Do not commit them to repo.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Private Key',
|
|
143
|
+
regex: /-----BEGIN PRIVATE KEY-----/,
|
|
144
|
+
category: 'A07:2021-Identification and Authentication Failures',
|
|
145
|
+
severity: 'CRITICAL',
|
|
146
|
+
remediation: 'Store secrets in environment variables or a secure vault. Do not commit them to repo.',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Generic API Key',
|
|
150
|
+
regex: /api_key\s*[:=]\s*['"][a-zA-Z0-9_-]{20,}['"]/,
|
|
151
|
+
category: 'A07:2021-Identification and Authentication Failures',
|
|
152
|
+
severity: 'HIGH',
|
|
153
|
+
remediation: 'Store secrets in environment variables or a secure vault. Do not commit them to repo.',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'Generic Password',
|
|
157
|
+
regex: /password\s*[:=]\s*['"][a-zA-Z0-9_\-!@#$%^&*]{6,}['"]/,
|
|
158
|
+
category: 'A07:2021-Identification and Authentication Failures',
|
|
159
|
+
severity: 'MEDIUM',
|
|
160
|
+
remediation: 'Store secrets in environment variables or a secure vault. Do not commit them to repo.',
|
|
161
|
+
},
|
|
162
|
+
];
|
|
@@ -7,41 +7,22 @@ exports.CodeScanner = void 0;
|
|
|
7
7
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
8
|
const glob_1 = require("glob");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const rules_1 = require("../rules");
|
|
10
11
|
class CodeScanner {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
];
|
|
12
|
+
config;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
39
16
|
async scan(projectPath) {
|
|
40
17
|
const results = [];
|
|
18
|
+
const ignorePatterns = ['node_modules/**', 'dist/**', '.git/**', '**/*.test.ts', '**/*.spec.ts'];
|
|
19
|
+
if (this.config?.ignore) {
|
|
20
|
+
ignorePatterns.push(...this.config.ignore);
|
|
21
|
+
}
|
|
41
22
|
const files = await (0, glob_1.glob)('**/*.{ts,js}', {
|
|
42
23
|
cwd: projectPath,
|
|
43
|
-
ignore:
|
|
44
|
-
nodir: true
|
|
24
|
+
ignore: ignorePatterns,
|
|
25
|
+
nodir: true,
|
|
45
26
|
});
|
|
46
27
|
for (const file of files) {
|
|
47
28
|
const fullPath = path_1.default.join(projectPath, file);
|
|
@@ -50,22 +31,22 @@ class CodeScanner {
|
|
|
50
31
|
const lines = content.split('\n');
|
|
51
32
|
for (let i = 0; i < lines.length; i++) {
|
|
52
33
|
const line = lines[i];
|
|
53
|
-
for (const
|
|
54
|
-
if (
|
|
34
|
+
for (const rule of rules_1.CODE_RULES) {
|
|
35
|
+
if (rule.regex.test(line)) {
|
|
55
36
|
results.push({
|
|
56
|
-
category:
|
|
57
|
-
severity:
|
|
58
|
-
description: `Potential Security Issue: ${
|
|
37
|
+
category: rule.category,
|
|
38
|
+
severity: rule.severity,
|
|
39
|
+
description: `Potential Security Issue: ${rule.name}`,
|
|
59
40
|
file: file,
|
|
60
41
|
line: i + 1,
|
|
61
42
|
snippet: line.trim(),
|
|
62
|
-
remediation:
|
|
43
|
+
remediation: rule.remediation || '',
|
|
63
44
|
});
|
|
64
45
|
}
|
|
65
46
|
}
|
|
66
47
|
}
|
|
67
48
|
}
|
|
68
|
-
catch (
|
|
49
|
+
catch (_err) {
|
|
69
50
|
// Ignore read errors
|
|
70
51
|
}
|
|
71
52
|
}
|
|
@@ -6,19 +6,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DependencyAuditor = void 0;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
8
|
const util_1 = __importDefault(require("util"));
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
9
11
|
const execPromise = util_1.default.promisify(child_process_1.exec);
|
|
10
12
|
class DependencyAuditor {
|
|
11
13
|
async scan(projectPath) {
|
|
14
|
+
let command = 'npm audit --json';
|
|
12
15
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const hasPnpmLock = await this.fileExists(projectPath, 'pnpm-lock.yaml');
|
|
17
|
+
const hasPackageLock = await this.fileExists(projectPath, 'package-lock.json');
|
|
18
|
+
if (hasPnpmLock && !hasPackageLock) {
|
|
19
|
+
command = 'pnpm audit --json';
|
|
20
|
+
}
|
|
21
|
+
await execPromise(command, { cwd: projectPath });
|
|
22
|
+
return [];
|
|
17
23
|
}
|
|
18
24
|
catch (error) {
|
|
19
|
-
// If it's a real error (not just vulnerabilities found), rethrow
|
|
20
25
|
if (!error.stdout) {
|
|
21
|
-
|
|
26
|
+
// If checking lockfiles failed, default to npm and swallow errors if it fails early
|
|
27
|
+
// console.error(`Error running ${command}:`, error);
|
|
22
28
|
return [];
|
|
23
29
|
}
|
|
24
30
|
try {
|
|
@@ -26,11 +32,20 @@ class DependencyAuditor {
|
|
|
26
32
|
return this.parseAuditReport(auditReport);
|
|
27
33
|
}
|
|
28
34
|
catch (parseError) {
|
|
29
|
-
console.error(
|
|
35
|
+
// console.error(`Failed to parse ${command} output`, parseError);
|
|
30
36
|
return [];
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
}
|
|
40
|
+
async fileExists(dir, file) {
|
|
41
|
+
try {
|
|
42
|
+
await promises_1.default.access(path_1.default.join(dir, file));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
parseAuditReport(report) {
|
|
35
50
|
const results = [];
|
|
36
51
|
// Check for errors in report (npm audit sometimes returns error structure)
|
|
@@ -42,11 +57,11 @@ class DependencyAuditor {
|
|
|
42
57
|
const vulnerability = detail;
|
|
43
58
|
if (vulnerability.severity) {
|
|
44
59
|
results.push({
|
|
45
|
-
category:
|
|
60
|
+
category: 'A06:2021-Vulnerable and Outdated Components',
|
|
46
61
|
severity: vulnerability.severity.toUpperCase(),
|
|
47
62
|
description: `Package '${name}' has a ${vulnerability.severity} vulnerability.`,
|
|
48
63
|
file: 'package.json',
|
|
49
|
-
remediation: vulnerability.fixAvailable ? "Run 'npm audit fix'" :
|
|
64
|
+
remediation: vulnerability.fixAvailable ? "Run 'npm audit fix'" : 'Update dependency manually',
|
|
50
65
|
});
|
|
51
66
|
}
|
|
52
67
|
}
|
|
@@ -7,19 +7,22 @@ exports.SecretScanner = void 0;
|
|
|
7
7
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
8
|
const glob_1 = require("glob");
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const rules_1 = require("../rules");
|
|
10
11
|
class SecretScanner {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{ name: 'Generic Password', regex: /password\s*[:=]\s*['"][a-zA-Z0-9_\-!@#$%^&*]{6,}['"]/, severity: 'MEDIUM' },
|
|
16
|
-
];
|
|
12
|
+
config;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
17
16
|
async scan(projectPath) {
|
|
18
17
|
const results = [];
|
|
18
|
+
const ignorePatterns = ['node_modules/**', 'dist/**', '.git/**'];
|
|
19
|
+
if (this.config?.ignore) {
|
|
20
|
+
ignorePatterns.push(...this.config.ignore);
|
|
21
|
+
}
|
|
19
22
|
const files = await (0, glob_1.glob)('**/*.{ts,js,json,env,txt,yml,yaml}', {
|
|
20
23
|
cwd: projectPath,
|
|
21
|
-
ignore:
|
|
22
|
-
nodir: true
|
|
24
|
+
ignore: ignorePatterns,
|
|
25
|
+
nodir: true,
|
|
23
26
|
});
|
|
24
27
|
for (const file of files) {
|
|
25
28
|
const fullPath = path_1.default.join(projectPath, file);
|
|
@@ -28,22 +31,22 @@ class SecretScanner {
|
|
|
28
31
|
const lines = content.split('\n');
|
|
29
32
|
for (let i = 0; i < lines.length; i++) {
|
|
30
33
|
const line = lines[i];
|
|
31
|
-
for (const
|
|
32
|
-
if (
|
|
34
|
+
for (const rule of rules_1.SECRET_RULES) {
|
|
35
|
+
if (rule.regex.test(line)) {
|
|
33
36
|
results.push({
|
|
34
|
-
category:
|
|
35
|
-
severity:
|
|
36
|
-
description: `Potential ${
|
|
37
|
+
category: rule.category,
|
|
38
|
+
severity: rule.severity,
|
|
39
|
+
description: `Potential ${rule.name} found`,
|
|
37
40
|
file: file,
|
|
38
41
|
line: i + 1,
|
|
39
42
|
snippet: line.trim().substring(0, 100), // Truncate lengthy lines
|
|
40
|
-
remediation:
|
|
43
|
+
remediation: rule.remediation || '',
|
|
41
44
|
});
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
|
-
catch (
|
|
49
|
+
catch (_err) {
|
|
47
50
|
// Ignore read errors
|
|
48
51
|
}
|
|
49
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Security scanner for Node.js projects checking for OWASP Top 10 risks",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
15
|
"prepublishOnly": "npm run build",
|
|
16
|
-
"test": "jest"
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
|
|
18
|
+
"format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'"
|
|
17
19
|
},
|
|
18
20
|
"keywords": [
|
|
19
21
|
"security",
|
|
@@ -30,11 +32,19 @@
|
|
|
30
32
|
"glob": "^13.0.0"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^9.39.2",
|
|
33
36
|
"@types/jest": "^30.0.0",
|
|
34
37
|
"@types/node": "^25.0.10",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
39
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
40
|
+
"eslint": "^9.39.2",
|
|
41
|
+
"eslint-config-prettier": "^10.1.8",
|
|
42
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
35
43
|
"jest": "^30.2.0",
|
|
36
|
-
"
|
|
44
|
+
"prettier": "^3.8.1",
|
|
45
|
+
"ts-jest": "^29.4.6",
|
|
37
46
|
"ts-node": "^10.9.2",
|
|
38
|
-
"typescript": "^5.9.3"
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"typescript-eslint": "^8.54.0"
|
|
39
49
|
}
|
|
40
|
-
}
|
|
50
|
+
}
|