xploitscan-shared-rules 1.8.1 → 1.9.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/index.js CHANGED
@@ -329,8 +329,69 @@ var TAINTED_REQUEST_OBJECTS = /* @__PURE__ */ new Set([
329
329
  "event"
330
330
  // Lambda
331
331
  ]);
332
+ var FS_READ_METHODS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
333
+ var PARSE_OBJECTS = /* @__PURE__ */ new Set([
334
+ "JSON",
335
+ "csv",
336
+ "papa",
337
+ "Papa",
338
+ "yaml",
339
+ "YAML",
340
+ "toml",
341
+ "qs",
342
+ "querystring"
343
+ ]);
344
+ var PARSE_BARE = /* @__PURE__ */ new Set(["parse", "parseSync"]);
345
+ var ARRAY_ELEMENT_METHODS = /* @__PURE__ */ new Set([
346
+ "map",
347
+ "filter",
348
+ "forEach",
349
+ "find",
350
+ "findIndex",
351
+ "flatMap",
352
+ "some",
353
+ "every"
354
+ ]);
355
+ var ARRAY_REDUCE_METHODS = /* @__PURE__ */ new Set(["reduce", "reduceRight"]);
356
+ function callPreservesTaint(node, rec) {
357
+ const callee = node.callee;
358
+ const anyArgTainted = () => node.arguments.some((a) => a.type !== "SpreadElement" && rec(a));
359
+ if (callee.type === "MemberExpression" && callee.property.type === "Identifier") {
360
+ const pname = callee.property.name;
361
+ if (FS_READ_METHODS.has(pname)) return anyArgTainted();
362
+ if (pname === "parse" || pname === "parseSync") {
363
+ const obj = callee.object;
364
+ if (obj.type === "Identifier" && PARSE_OBJECTS.has(obj.name)) return anyArgTainted();
365
+ }
366
+ }
367
+ if (callee.type === "Identifier") {
368
+ if (FS_READ_METHODS.has(callee.name)) return anyArgTainted();
369
+ if (PARSE_BARE.has(callee.name)) return anyArgTainted();
370
+ }
371
+ return false;
372
+ }
332
373
  function buildTaintMap(parsed) {
333
374
  const tainted = /* @__PURE__ */ new Set();
375
+ function markPatternTainted(target) {
376
+ if (target.type === "Identifier") {
377
+ tainted.add(target.name);
378
+ } else if (target.type === "ObjectPattern") {
379
+ for (const prop of target.properties) {
380
+ if (prop.type === "ObjectProperty" && prop.value.type === "Identifier") {
381
+ tainted.add(prop.value.name);
382
+ } else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
383
+ tainted.add(prop.argument.name);
384
+ }
385
+ }
386
+ } else if (target.type === "ArrayPattern") {
387
+ for (const el of target.elements) {
388
+ if (el && el.type === "Identifier") tainted.add(el.name);
389
+ else if (el && el.type === "RestElement" && el.argument.type === "Identifier") {
390
+ tainted.add(el.argument.name);
391
+ }
392
+ }
393
+ }
394
+ }
334
395
  function reachesRequestIdent(node) {
335
396
  if (!node) return false;
336
397
  if (node.type === "Identifier") return TAINTED_REQUEST_OBJECTS.has(node.name);
@@ -401,6 +462,7 @@ function buildTaintMap(parsed) {
401
462
  }
402
463
  if (node.type === "CallExpression") {
403
464
  if (nodeIsTaintedSource(node)) return true;
465
+ if (callPreservesTaint(node, exprIsTainted)) return true;
404
466
  if (node.callee.type === "MemberExpression") {
405
467
  if (exprIsTainted(node.callee.object)) return true;
406
468
  const obj = node.callee.object;
@@ -421,6 +483,16 @@ function buildTaintMap(parsed) {
421
483
  if (node.type === "AwaitExpression") {
422
484
  return exprIsTainted(node.argument);
423
485
  }
486
+ if (node.type === "ArrayExpression") {
487
+ return node.elements.some(
488
+ (el) => el != null && el.type === "SpreadElement" && exprIsTainted(el.argument)
489
+ );
490
+ }
491
+ if (node.type === "ObjectExpression") {
492
+ return node.properties.some(
493
+ (p) => p.type === "SpreadElement" && exprIsTainted(p.argument)
494
+ );
495
+ }
424
496
  return false;
425
497
  }
426
498
  traverse(parsed.ast, {
@@ -455,6 +527,32 @@ function buildTaintMap(parsed) {
455
527
  if (exprIsTainted(node.right)) {
456
528
  tainted.add(node.left.name);
457
529
  }
530
+ },
531
+ // for (const row of <tainted>) → row tainted (and destructured names).
532
+ ForOfStatement(path) {
533
+ const node = path.node;
534
+ if (node.type !== "ForOfStatement") return;
535
+ if (!exprIsTainted(node.right)) return;
536
+ const decl = node.left;
537
+ const target = decl.type === "VariableDeclaration" && decl.declarations[0] ? decl.declarations[0].id : decl;
538
+ markPatternTainted(target);
539
+ },
540
+ // Array-iteration element taint: <tainted>.map((row) => …) marks `row`
541
+ // tainted inside the callback. Gated on the receiver already being
542
+ // tainted, so this never originates taint.
543
+ CallExpression(path) {
544
+ const node = path.node;
545
+ if (node.type !== "CallExpression") return;
546
+ const callee = node.callee;
547
+ if (callee.type !== "MemberExpression" || callee.property.type !== "Identifier") return;
548
+ const method = callee.property.name;
549
+ const isReduce = ARRAY_REDUCE_METHODS.has(method);
550
+ if (!ARRAY_ELEMENT_METHODS.has(method) && !isReduce) return;
551
+ if (!exprIsTainted(callee.object)) return;
552
+ const cb = node.arguments[0];
553
+ if (!cb || cb.type !== "ArrowFunctionExpression" && cb.type !== "FunctionExpression") return;
554
+ const elemParam = cb.params[isReduce ? 1 : 0];
555
+ if (elemParam) markPatternTainted(elemParam);
458
556
  }
459
557
  });
460
558
  const isTainted = (node) => {
@@ -480,6 +578,7 @@ function buildTaintMap(parsed) {
480
578
  return isTainted(node.object);
481
579
  }
482
580
  if (node.type === "CallExpression") {
581
+ if (callPreservesTaint(node, isTainted)) return true;
483
582
  if (node.callee.type === "MemberExpression") {
484
583
  if (isTainted(node.callee.object)) return true;
485
584
  const obj = node.callee.object;
@@ -497,6 +596,16 @@ function buildTaintMap(parsed) {
497
596
  }
498
597
  }
499
598
  }
599
+ if (node.type === "ArrayExpression") {
600
+ return node.elements.some(
601
+ (el) => el != null && el.type === "SpreadElement" && isTainted(el.argument)
602
+ );
603
+ }
604
+ if (node.type === "ObjectExpression") {
605
+ return node.properties.some(
606
+ (p) => p.type === "SpreadElement" && isTainted(p.argument)
607
+ );
608
+ }
500
609
  return false;
501
610
  };
502
611
  return {