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 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
+ ![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)
7
+ ![Values: Warning Only](https://img.shields.io/badge/Mode-Warning%20Only-yellow)
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
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ }