diff --git a/.env b/.env index 3cd8a78..1e4ec0e 100644 --- a/.env +++ b/.env @@ -32,3 +32,6 @@ GENERIC_LONG = ' = "abcdefghijklmnopqrstuvwxABCDEFGHIJKLMNOPQRSTUVWX123"' # --- Added by ShieldX on 3/30/2026 --- MONGO_URI="mongodb://localhost:27017/agemi" + +# --- Added by ShieldX on 4/12/2026 --- +SUPABASE_URL="postgresql://postgres.ptlklskdckgvhwbfnhyq:jOj23q0Do9maiqa5@aws-1-eu-west-1.pooler.supabase.com:5432/postgres" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c74b7e..7c3448b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ First off, thank you for considering contributing to ShieldX! šŸŽ‰ ## šŸš€ Quick Start 1. Fork the repository -2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/shieldx.git` +2. Clone your fork: `git clone https://github.com/zeemscript/shieldx.git` 3. Install dependencies: `npm install` 4. Link for local development: `npm link` 5. Create a branch: `git checkout -b feature/amazing-feature` diff --git a/src/commands/deps.js b/src/commands/deps.js new file mode 100644 index 0000000..6d76a5a --- /dev/null +++ b/src/commands/deps.js @@ -0,0 +1,174 @@ +import fs from "fs"; +import path from "path"; +import chalk from "chalk"; + +const TOP_PACKAGES = [ + "express", "react", "react-dom", "lodash", "moment", "chalk", "commander", + "inquirer", "axios", "mongoose", "tailwindcss", "typescript", "jest", + "webpack", "eslint", "prettier", "dotenv", "next", "vue", "angular", + "cross-env", "body-parser", "uuid", "crypto-js", "fs-extra", "uuid", + "request", "node-fetch", "underscore" +]; + +const KNOWN_MALICIOUS = { + "crossenv": "Typosquatting of 'cross-env'. Contains malicious payload.", + "eslint-scope-5": "Typosquat/Malicious version.", + "flatmap-stream": "Injected malicious code targeting bitcoin wallets.", + "event-stream-3.3.6": "Compromised version of event-stream.", + "rc-all": "Malicious typosquat.", + "buble": "Deprecated/Vulnerable if certain old versions.", + "request": "Deprecated. Use 'axios' or 'node-fetch' instead.", + "electorn": "Typosquatting of 'electron'. Malicious." +}; + +/** + * Very simple Levenshtein distance + */ +function levenshteinDistance(a, b) { + const matrix = []; + for (let i = 0; i <= b.length; i++) { + matrix[i] = [i]; + } + for (let j = 0; j <= a.length; j++) { + matrix[0][j] = j; + } + for (let i = 1; i <= b.length; i++) { + for (let j = 1; j <= a.length; j++) { + if (b.charAt(i - 1) == a.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, + Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1) + ); + } + } + } + return matrix[b.length][a.length]; +} + +export function scanDeps(dir = ".", options = {}) { + try { + const pkgPath = path.join(dir, "package.json"); + + if (!fs.existsSync(pkgPath)) { + console.error(chalk.red(`āŒ Error: Could not find package.json in ${dir}`)); + process.exit(1); + } + + if (!options.quiet && !options.json) { + console.log(chalk.blue(`šŸ“¦ Scanning dependencies in ${pkgPath}...\n`)); + } + + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); + const depsObj = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; + const depsKeys = Object.keys(depsObj); + + let issuesFound = 0; + const findings = []; + const severityCounts = { critical: 0, high: 0, medium: 0, low: 0 }; + + depsKeys.forEach(dep => { + // 1. Check against known malicious + if (KNOWN_MALICIOUS[dep]) { + issuesFound++; + severityCounts.critical++; + findings.push({ + package: dep, + version: depsObj[dep], + type: "Malicious/Deprecated Package", + severity: "critical", + message: KNOWN_MALICIOUS[dep] + }); + return; // skip further checks for this + } + + // 2. Check for Typosquatting against top packages + // only check if dep is NOT exactly in the top packages + // (if it IS in TOP_PACKAGES, it's the real one) + if (!TOP_PACKAGES.includes(dep)) { + for (const top of TOP_PACKAGES) { + const dist = levenshteinDistance(dep.toLowerCase(), top.toLowerCase()); + + // If distance is 1, or distance is 2 but it's a long package name + if (dist === 1 || (dist === 2 && top.length > 6)) { + // It looks suspicious! + issuesFound++; + severityCounts.high++; + findings.push({ + package: dep, + version: depsObj[dep], + type: "Potential Typosquatting", + severity: "high", + message: `Looks very similar to popular package '${top}'. Please verify this is intentional.` + }); + break; // Stop checking against other top packages once flagged + } + } + } + }); + + if (options.json) { + console.log( + JSON.stringify( + { + scannedFile: pkgPath, + packagesScanned: depsKeys.length, + issuesFound, + severityCounts, + findings, + }, + null, + 2 + ) + ); + return; + } + + if (issuesFound === 0) { + console.log(chalk.green("āœ… No malicious or suspicious dependencies found! šŸŽ‰")); + console.log(chalk.gray(` Scanned ${depsKeys.length} packages`)); + } else { + findings.forEach((finding) => { + if (!options.quiet) { + const severityColor = { + critical: chalk.bgRed.white.bold, + high: chalk.red, + medium: chalk.yellow, + low: chalk.blue, + }; + + console.log( + severityColor[finding.severity]( + `🚨 [${finding.severity.toUpperCase()}] ${finding.type}` + ) + chalk.gray(` in package.json`) + ); + console.log(chalk.yellow(` Package: ${finding.package}@${finding.version}`)); + console.log(chalk.white(` ${finding.message}\n`)); + } + }); + + console.log(chalk.red.bold(`\nāš ļø Dependency Security Report:`)); + console.log(chalk.red(` Total issues: ${issuesFound}`)); + console.log(chalk.gray(` Packages scanned: ${depsKeys.length}`)); + console.log(); + if (severityCounts.critical > 0) + console.log(chalk.bgRed.white(` CRITICAL: ${severityCounts.critical} `)); + if (severityCounts.high > 0) + console.log(chalk.red(` High: ${severityCounts.high}`)); + if (severityCounts.medium > 0) + console.log(chalk.yellow(` Medium: ${severityCounts.medium}`)); + if (severityCounts.low > 0) + console.log(chalk.blue(` Low: ${severityCounts.low}`)); + } + + if (process.env.NODE_ENV !== "test") { + process.exit(issuesFound > 0 ? 1 : 0); + } + } catch (err) { + console.error(chalk.red(`āŒ Error: ${err.message}`)); + if (process.env.NODE_ENV !== "test") { + process.exit(1); + } + } +} diff --git a/src/index.js b/src/index.js index a1b72ba..241595d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import { Command } from "commander"; import chalk from "chalk"; import compare from "./commands/compare.js"; +import { scanDeps } from "./commands/deps.js"; import generate from "./commands/generate.js"; import { scanCode } from "./commands/scan.js"; import validate from "./commands/validate.js"; @@ -315,4 +316,21 @@ ${chalk.yellow("šŸ’” Tip:")} The audit combines ${chalk.green( ) .action((options) => audit(options)); +// Deps command - Scan dependencies for typosquatting and malicious packages +program + .command("deps [dir]") + .description("šŸ“¦ Scan package.json for typosquatting and malicious dependencies") + .option("-j, --json", "Output results in JSON format") + .option("-q, --quiet", "Suppress non-error output") + .addHelpText( + "after", + ` +${chalk.cyan("Examples:")} + $ shieldx deps ${chalk.gray("# Scan current directory")} + $ shieldx deps ./client ${chalk.gray("# Scan specific directory")} + $ shieldx deps --json ${chalk.gray("# JSON output for CI/CD")} + ` + ) + .action((dir, options) => scanDeps(dir, options)); + program.parse(process.argv); diff --git a/test_dir/test_supabase.js b/test_dir/test_supabase.js index f057d57..59879e1 100644 --- a/test_dir/test_supabase.js +++ b/test_dir/test_supabase.js @@ -1,2 +1,2 @@ -const SUPABASE_URL = 'postgresql://postgres.ptlklskdckgvhwbfnhyq:jOj23q0Do9maiqa5@aws-1-eu-west-1.pooler.supabase.com:5432/postgres'; +const SUPABASE_URL = process.env.SUPABASE_URL; const OLD_MONGO = 'mongodb://localhost:27017/agemi'; diff --git a/test_supabase.js b/test_supabase.js deleted file mode 100644 index f057d57..0000000 --- a/test_supabase.js +++ /dev/null @@ -1,2 +0,0 @@ -const SUPABASE_URL = 'postgresql://postgres.ptlklskdckgvhwbfnhyq:jOj23q0Do9maiqa5@aws-1-eu-west-1.pooler.supabase.com:5432/postgres'; -const OLD_MONGO = 'mongodb://localhost:27017/agemi';