xploitscan-shared-rules 1.10.0 → 1.11.1

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.cjs CHANGED
@@ -1080,9 +1080,12 @@ var SERVER_SIDE_PATH_RE = new RegExp(
1080
1080
  ].join("|"),
1081
1081
  "i"
1082
1082
  );
1083
- function isServerSideFile(filePath) {
1083
+ var SERVER_SIDE_CONTENT_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies)\b|\b(?:app|router)\.(?:get|post|put|patch|delete|use|all)\s*\(|\bctx\.request\b|\bevent\.(?:body|queryStringParameters|pathParameters)\b|\(\s*req\s*,\s*res\b/;
1084
+ function isServerSideFile(filePath, content) {
1084
1085
  if (isTestFile(filePath)) return false;
1085
- return SERVER_SIDE_PATH_RE.test(filePath);
1086
+ if (SERVER_SIDE_PATH_RE.test(filePath)) return true;
1087
+ if (content && SERVER_SIDE_CONTENT_RE.test(content)) return true;
1088
+ return false;
1086
1089
  }
1087
1090
  var CONFIG_FILE_PATTERN = new RegExp(
1088
1091
  [
@@ -1103,7 +1106,7 @@ function isCommentLine(content, matchIndex) {
1103
1106
  function isInsideFixMessage(content, matchIndex) {
1104
1107
  const lineStart = content.lastIndexOf("\n", matchIndex - 1) + 1;
1105
1108
  const lineText = content.substring(lineStart, content.indexOf("\n", matchIndex));
1106
- return /(?:fix|description|message|suggestion|hint|help|example|doc|comment)\s*[:=(]/i.test(lineText) || /return\s*["'`].*\b(?:Use|Replace|Add|Move|Set|Enable|Disable|Never|Don't|Do not|Instead)\b/i.test(lineText);
1109
+ return /(?:fix|description|message|suggestion|hint|help|example|doc|comment)\s*[:=]\s*["'`]/i.test(lineText) || /return\s*["'`].*\b(?:Use|Replace|Add|Move|Set|Enable|Disable|Never|Don't|Do not|Instead)\b/i.test(lineText);
1107
1110
  }
1108
1111
  function isInlineSilenced(content, matchIndex, ruleId) {
1109
1112
  const lines = content.split("\n");
@@ -1295,7 +1298,7 @@ var missingAuthMiddleware = {
1295
1298
  category: "Authentication",
1296
1299
  description: "API routes without authentication checks allow unauthorized access.",
1297
1300
  check(content, filePath) {
1298
- if (!isServerSideFile(filePath)) return [];
1301
+ if (!isServerSideFile(filePath, content)) return [];
1299
1302
  const routePatterns = [
1300
1303
  // Express/Hono style
1301
1304
  /\.(get|post|put|patch|delete)\s*\(\s*["'`][^"'`]+["'`]\s*,\s*(?:async\s+)?\(?(?:req|c|ctx)/gi,
@@ -2613,7 +2616,7 @@ var exposedStackTraces = {
2613
2616
  category: "Information Leakage",
2614
2617
  description: "Returning error.stack or detailed error messages in API responses reveals internal code paths, file structure, and dependencies to attackers.",
2615
2618
  check(content, filePath) {
2616
- if (!isServerSideFile(filePath)) return [];
2619
+ if (!isServerSideFile(filePath, content)) return [];
2617
2620
  const matches = [];
2618
2621
  const patterns = [
2619
2622
  // Sending stack trace in response
@@ -2711,7 +2714,7 @@ var ssrfVulnerability = {
2711
2714
  category: "Injection",
2712
2715
  description: "Fetching URLs from user input without validation allows attackers to access internal services, cloud metadata endpoints (169.254.169.254), and private networks.",
2713
2716
  check(content, filePath) {
2714
- if (!isServerSideFile(filePath)) return [];
2717
+ if (!isServerSideFile(filePath, content)) return [];
2715
2718
  const matches = [];
2716
2719
  const hasValidation = /allowedHosts|allowedDomains|allowedUrls|safeDomain|whitelist|urlValidator|new URL.*hostname.*includes|isAllowedUrl|validateUrl|isValidUrl/i.test(content);
2717
2720
  if (hasValidation) return [];
@@ -2768,7 +2771,7 @@ var massAssignment = {
2768
2771
  category: "Authorization",
2769
2772
  description: "Spreading or assigning request body directly into database models allows attackers to set fields they shouldn't (e.g., isAdmin, role, verified).",
2770
2773
  check(content, filePath) {
2771
- if (!isServerSideFile(filePath)) return [];
2774
+ if (!isServerSideFile(filePath, content)) return [];
2772
2775
  const hasSanitization = /pick\(|omit\(|allowedFields|sanitize|whitelist|permit|strong_params/i.test(content);
2773
2776
  if (hasSanitization) return [];
2774
2777
  const matches = [];
@@ -2920,7 +2923,7 @@ var logInjection = {
2920
2923
  category: "Injection",
2921
2924
  description: "Logging unsanitized user input allows attackers to forge log entries, inject malicious content, or exploit log aggregation systems via newlines and special characters.",
2922
2925
  check(content, filePath) {
2923
- if (!isServerSideFile(filePath)) return [];
2926
+ if (!isServerSideFile(filePath, content)) return [];
2924
2927
  const hasSanitization = /replace\s*\(\s*\/\[?\\r\\n\]|sanitizeLog|stripNewlines|sanitizeForLog/i.test(content);
2925
2928
  if (hasSanitization) return [];
2926
2929
  const matches = [];
@@ -2986,7 +2989,7 @@ var weakPasswordRequirements = {
2986
2989
  if (!/(?:password|passwd|pwd)/i.test(content)) return [];
2987
2990
  const isPasswordContext = /(?:register|signup|sign.up|createUser|create.user|changePassword|resetPassword|set.password|validatePassword|validate.password|passwordPolicy|password.policy)/i.test(
2988
2991
  content
2989
- ) || isServerSideFile(filePath);
2992
+ ) || isServerSideFile(filePath, content);
2990
2993
  const matches = [];
2991
2994
  if (isPasswordContext) {
2992
2995
  const weakThresholdPatterns = [
@@ -4140,8 +4143,8 @@ var xxeVulnerability = {
4140
4143
  if (!/xml|parseXml|parseXML|DOMParser|SAXParser|etree|lxml|libxml/i.test(content)) return [];
4141
4144
  const matches = [];
4142
4145
  const patterns = [
4143
- /\.parseXm?l\s*\(/gi,
4144
- // catches parseXml (libxmljs) AND parseXML
4146
+ /\.parseXm?l(?:String)?\s*\(/gi,
4147
+ // parseXml / parseXML / parseXmlString (libxmljs)
4145
4148
  // NOTE: the browser `new DOMParser()` is intentionally NOT flagged.
4146
4149
  // Per the HTML/XML spec, DOMParser.parseFromString does not resolve
4147
4150
  // external entities, so it is not an XXE sink — flagging it produced a
@@ -4327,7 +4330,7 @@ var exposedAdminRoutes = {
4327
4330
  category: "Information Leakage",
4328
4331
  description: "Routes like /admin, /debug, /phpinfo, or /actuator without authentication expose sensitive controls and information to attackers.",
4329
4332
  check(content, filePath) {
4330
- if (!isServerSideFile(filePath)) return [];
4333
+ if (!isServerSideFile(filePath, content)) return [];
4331
4334
  const matches = [];
4332
4335
  const patterns = [
4333
4336
  /[.'"]\s*(?:get|use|all)\s*\(\s*["'`]\/(?:admin|debug|_debug|__debug__|phpinfo|actuator|graphiql|playground|swagger|reset|seed|test|dev|mock)["'`]/gi
@@ -4419,7 +4422,7 @@ var missingContentDisposition = {
4419
4422
  description: "File download endpoints without Content-Disposition headers may render files inline, leading to XSS if the file contains HTML/JS.",
4420
4423
  check(content, filePath) {
4421
4424
  if (!/(?:download|sendFile|send_file|pipe|createReadStream)/i.test(content)) return [];
4422
- if (!isServerSideFile(filePath)) return [];
4425
+ if (!isServerSideFile(filePath, content)) return [];
4423
4426
  if (/Content-Disposition|attachment|download/i.test(content)) return [];
4424
4427
  return findMatches(
4425
4428
  content,
@@ -4516,7 +4519,7 @@ var unprotectedDownload = {
4516
4519
  description: "File download endpoints that accept user-controlled filenames without path validation allow directory traversal to read arbitrary files.",
4517
4520
  check(content, filePath) {
4518
4521
  if (!/(?:download|sendFile|send_file)/i.test(content)) return [];
4519
- if (!isServerSideFile(filePath)) return [];
4522
+ if (!isServerSideFile(filePath, content)) return [];
4520
4523
  const matches = [];
4521
4524
  if (/(?:sendFile|download|send_file)\s*\([^)]*(?:req\.|params\.|query\.|body\.)/i.test(content)) {
4522
4525
  const hasValidation = /path\.resolve|path\.normalize|path\.join.*__dirname|realpath|includes\s*\(\s*["']\.\./i.test(content);
@@ -4545,7 +4548,7 @@ var commandInjection = {
4545
4548
  const matches = [];
4546
4549
  const patterns = [
4547
4550
  // Node.js — require standalone exec/execSync, not db.exec() or conn.exec()
4548
- /(?<![.\w])(?:exec|execSync)\s*\(\s*(?:`[^`]*\$\{|["'][^"']*\+\s*(?:req\.|body\.|input|params|args|user))/gi,
4551
+ /(?<![.\w])(?:exec|execSync)\s*\(\s*(?:`[^`]*\$\{|["'][^"']*["']\s*\+\s*(?:req\.|body\.|input|params|args|user))/gi,
4549
4552
  /child_process.*exec\s*\(\s*(?!["'`])/g,
4550
4553
  // spawn / execFile / exec with shell: true AND a template literal
4551
4554
  // first arg. `shell: true` converts the first argument into a string