xploitscan 1.0.7 → 1.0.8

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/index.js CHANGED
@@ -335,15 +335,20 @@ var hardcodedSecrets = {
335
335
  // Database URLs with credentials
336
336
  /(?:postgres|mysql|mongodb(?:\+srv)?):\/\/[^:]+:[^@]+@[^/\s"'`]+/gi
337
337
  ];
338
+ const fixMessages = [
339
+ "Move this API key to an environment variable. If this key has been committed, rotate it immediately \u2014 it may have already been scraped by bots.",
340
+ "AWS access key detected \u2014 may grant full account access (EC2, S3, IAM, billing). Rotate immediately in AWS Console \u2192 IAM \u2192 Security Credentials. Use IAM roles or environment variables instead.",
341
+ "Stripe key detected. sk_live_ keys can process real charges, issue refunds, and access customer payment data. sk_test_ keys are lower risk but should still not be committed. Rotate in Stripe Dashboard \u2192 Developers \u2192 API Keys.",
342
+ "Supabase key detected. Service role keys bypass Row Level Security and grant full database read/write access. Move to a server-side environment variable immediately.",
343
+ "OpenAI API key detected \u2014 grants full API access and can incur charges. Rotate at platform.openai.com \u2192 API Keys.",
344
+ "Hardcoded token or password detected. Move to an environment variable and rotate the credential if it has been committed to version control.",
345
+ "Private key found in source code. If this has been committed to version control, consider the key compromised \u2014 generate a new key pair and revoke the old one.",
346
+ "Database credentials in connection string. An attacker with this URL has full database access. Move to an environment variable, restrict network access, and rotate the password."
347
+ ];
338
348
  const matches = [];
339
- for (const pattern of patterns) {
340
- const rawMatches = findMatches(
341
- content,
342
- pattern,
343
- hardcodedSecrets,
344
- filePath,
345
- () => "Move this secret to an environment variable and add it to .env (not committed to git). Use .env.example to document the required variables."
346
- );
349
+ for (let pi = 0; pi < patterns.length; pi++) {
350
+ const pattern = patterns[pi];
351
+ const rawMatches = findMatches(content, pattern, hardcodedSecrets, filePath, () => fixMessages[pi]);
347
352
  for (const rm3 of rawMatches) {
348
353
  const lineText = content.split("\n")[rm3.line - 1] || "";
349
354
  const trimmed = lineText.trimStart();
@@ -1144,51 +1149,52 @@ function detectFramework(files) {
1144
1149
  if (frameworks.size === 0) frameworks.add("unknown");
1145
1150
  return [...frameworks];
1146
1151
  }
1147
- function calculateGrade(findings, totalFiles) {
1152
+ function calculateGrade(findings, _totalFiles) {
1148
1153
  if (findings.length === 0) {
1149
- return { grade: "A+", score: 100, summary: "No security issues detected. Excellent!" };
1154
+ return { grade: "A+", score: 100, summary: "No security issues detected. Excellent." };
1150
1155
  }
1151
- let deductions = 0;
1156
+ let critical = 0, high = 0, medium = 0, low = 0;
1152
1157
  for (const f of findings) {
1153
- switch (f.severity) {
1154
- case "critical":
1155
- deductions += 15;
1156
- break;
1157
- case "high":
1158
- deductions += 8;
1159
- break;
1160
- case "medium":
1161
- deductions += 4;
1162
- break;
1163
- case "low":
1164
- deductions += 1;
1165
- break;
1166
- }
1167
- }
1168
- const sizeBuffer = Math.min(Math.log2(Math.max(totalFiles, 1)) * 2, 15);
1169
- const score = Math.max(0, Math.min(100, 100 - deductions + sizeBuffer));
1158
+ if (f.severity === "critical") critical++;
1159
+ else if (f.severity === "high") high++;
1160
+ else if (f.severity === "medium") medium++;
1161
+ else if (f.severity === "low") low++;
1162
+ }
1163
+ const deductions = critical * 15 + high * 7 + medium * 3 + low * 1;
1164
+ const rawScore = Math.max(0, 100 - deductions);
1170
1165
  let grade;
1166
+ if (rawScore >= 97) grade = "A+";
1167
+ else if (rawScore >= 90) grade = "A";
1168
+ else if (rawScore >= 80) grade = "B";
1169
+ else if (rawScore >= 70) grade = "C";
1170
+ else if (rawScore >= 60) grade = "D";
1171
+ else grade = "F";
1172
+ const capGrade = (cap) => {
1173
+ const order = ["A+", "A", "B", "C", "D", "F"];
1174
+ return order.indexOf(grade) < order.indexOf(cap) ? cap : grade;
1175
+ };
1176
+ if (critical >= 1) grade = capGrade("D");
1177
+ else if (high >= 3) grade = capGrade("D");
1178
+ else if (high >= 1) grade = capGrade("B");
1179
+ else if (medium >= 5) grade = capGrade("B");
1180
+ else if (medium >= 1) grade = capGrade("A");
1171
1181
  let summary;
1172
- if (score >= 95) {
1173
- grade = "A+";
1174
- summary = "Excellent security posture with minimal issues.";
1175
- } else if (score >= 85) {
1176
- grade = "A";
1177
- summary = "Strong security with a few minor concerns.";
1178
- } else if (score >= 70) {
1179
- grade = "B";
1180
- summary = "Good security but some issues need attention.";
1181
- } else if (score >= 55) {
1182
- grade = "C";
1183
- summary = "Fair security \u2014 several vulnerabilities should be fixed.";
1184
- } else if (score >= 35) {
1185
- grade = "D";
1186
- summary = "Poor security \u2014 critical issues require immediate attention.";
1182
+ if (critical > 0) {
1183
+ summary = `${critical} critical ${critical === 1 ? "vulnerability" : "vulnerabilities"} require immediate attention.`;
1184
+ } else if (high >= 3) {
1185
+ summary = `${high} high-severity issues require urgent attention.`;
1186
+ } else if (high > 0) {
1187
+ summary = `${high} high-severity ${high === 1 ? "issue needs" : "issues need"} attention.`;
1188
+ } else if (medium >= 5) {
1189
+ summary = `${medium} medium-severity issues to address.`;
1190
+ } else if (medium > 0) {
1191
+ summary = `Clean of critical and high issues. ${medium} medium-severity ${medium === 1 ? "issue" : "issues"} to review.`;
1192
+ } else if (low > 0) {
1193
+ summary = `Clean of critical, high, and medium issues. ${low} low-severity best-practice ${low === 1 ? "note" : "notes"}.`;
1187
1194
  } else {
1188
- grade = "F";
1189
- summary = "Failing \u2014 serious vulnerabilities present. Fix critical issues immediately.";
1195
+ summary = "No security issues detected.";
1190
1196
  }
1191
- return { grade, score: Math.round(score), summary };
1197
+ return { grade, score: rawScore, summary };
1192
1198
  }
1193
1199
  var complianceMap = {
1194
1200
  VC001: { owasp: "A07:2021", cwe: "CWE-798" },
@@ -1321,7 +1327,29 @@ var complianceMap = {
1321
1327
  VC128: { owasp: "A05:2021", cwe: "CWE-444" },
1322
1328
  VC129: { owasp: "A02:2021", cwe: "CWE-311" },
1323
1329
  VC130: { owasp: "A07:2021", cwe: "CWE-307" },
1324
- VC131: { owasp: "A06:2021", cwe: "CWE-1104" }
1330
+ VC131: { owasp: "A06:2021", cwe: "CWE-1104" },
1331
+ // VC132–VC145: Service-specific API key detection
1332
+ VC132: { owasp: "A07:2021", cwe: "CWE-798" },
1333
+ VC133: { owasp: "A07:2021", cwe: "CWE-798" },
1334
+ VC134: { owasp: "A07:2021", cwe: "CWE-798" },
1335
+ VC135: { owasp: "A07:2021", cwe: "CWE-798" },
1336
+ VC136: { owasp: "A07:2021", cwe: "CWE-798" },
1337
+ VC137: { owasp: "A07:2021", cwe: "CWE-798" },
1338
+ VC138: { owasp: "A07:2021", cwe: "CWE-798" },
1339
+ VC139: { owasp: "A07:2021", cwe: "CWE-798" },
1340
+ VC140: { owasp: "A07:2021", cwe: "CWE-798" },
1341
+ VC141: { owasp: "A07:2021", cwe: "CWE-798" },
1342
+ VC142: { owasp: "A07:2021", cwe: "CWE-798" },
1343
+ VC143: { owasp: "A07:2021", cwe: "CWE-798" },
1344
+ VC144: { owasp: "A07:2021", cwe: "CWE-798" },
1345
+ VC145: { owasp: "A07:2021", cwe: "CWE-798" },
1346
+ // VC146–VC151: Secret exposure path analysis
1347
+ VC146: { owasp: "A07:2021", cwe: "CWE-598" },
1348
+ VC147: { owasp: "A09:2021", cwe: "CWE-532" },
1349
+ VC148: { owasp: "A09:2021", cwe: "CWE-209" },
1350
+ VC149: { owasp: "A07:2021", cwe: "CWE-798" },
1351
+ VC150: { owasp: "A07:2021", cwe: "CWE-615" },
1352
+ VC151: { owasp: "A07:2021", cwe: "CWE-214" }
1325
1353
  };
1326
1354
  var consoleLogProduction = {
1327
1355
  id: "VC097",
@@ -3820,7 +3848,7 @@ async function cursorInstallCommand(opts = {}) {
3820
3848
  var program = new Command();
3821
3849
  program.name("xploitscan").description(
3822
3850
  "AI security scanner for vibe-coded apps. Find vulnerabilities before attackers do."
3823
- ).version("1.0.7");
3851
+ ).version("1.0.8");
3824
3852
  program.command("scan").description("Scan a directory for security vulnerabilities").argument("[directory]", "Directory to scan", ".").option("--no-ai", "Skip AI-powered analysis").option("-f, --format <format>", "Output format: terminal, json, sarif", "terminal").option("-v, --verbose", "Show detailed output", false).option("--diff [base]", "Scan only files changed vs base branch (default: main)").option("-w, --watch", "Watch for file changes and re-scan automatically", false).action(async (directory, opts) => {
3825
3853
  await scanCommand(directory, {
3826
3854
  directory,