circle-ir 3.58.0 → 3.62.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.
Files changed (65) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +58 -17
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/html/html-merge.d.ts.map +1 -1
  5. package/dist/analysis/html/html-merge.js +10 -0
  6. package/dist/analysis/html/html-merge.js.map +1 -1
  7. package/dist/analysis/interprocedural.d.ts.map +1 -1
  8. package/dist/analysis/interprocedural.js +44 -11
  9. package/dist/analysis/interprocedural.js.map +1 -1
  10. package/dist/analysis/passes/language-sources-pass.d.ts +7 -1
  11. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  12. package/dist/analysis/passes/language-sources-pass.js +283 -15
  13. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  14. package/dist/analysis/passes/missing-public-doc-pass.d.ts.map +1 -1
  15. package/dist/analysis/passes/missing-public-doc-pass.js +2 -1
  16. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -1
  17. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  18. package/dist/analysis/passes/sink-filter-pass.js +4 -1
  19. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  20. package/dist/analysis/passes/taint-propagation-pass.js +2 -1
  21. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  22. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  23. package/dist/analysis/passes/weak-random-pass.js +2 -1
  24. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  25. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  26. package/dist/analysis/taint-matcher.js +29 -7
  27. package/dist/analysis/taint-matcher.js.map +1 -1
  28. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  29. package/dist/analysis/taint-propagation.js +20 -0
  30. package/dist/analysis/taint-propagation.js.map +1 -1
  31. package/dist/analyzer.d.ts.map +1 -1
  32. package/dist/analyzer.js +19 -2
  33. package/dist/analyzer.js.map +1 -1
  34. package/dist/browser/circle-ir.js +512 -51
  35. package/dist/core/circle-ir-core.cjs +243 -26
  36. package/dist/core/circle-ir-core.js +243 -26
  37. package/dist/core/extractors/calls.js +181 -1
  38. package/dist/core/extractors/calls.js.map +1 -1
  39. package/dist/core/extractors/cfg.js +1 -1
  40. package/dist/core/extractors/cfg.js.map +1 -1
  41. package/dist/core/extractors/dfg.js +29 -3
  42. package/dist/core/extractors/dfg.js.map +1 -1
  43. package/dist/core/extractors/imports.js +1 -1
  44. package/dist/core/extractors/imports.js.map +1 -1
  45. package/dist/core/extractors/runtime-registrations.js +1 -1
  46. package/dist/core/extractors/runtime-registrations.js.map +1 -1
  47. package/dist/core/extractors/types.js +1 -1
  48. package/dist/core/extractors/types.js.map +1 -1
  49. package/dist/core/parser.d.ts +1 -1
  50. package/dist/core/parser.d.ts.map +1 -1
  51. package/dist/graph/scope-graph.d.ts.map +1 -1
  52. package/dist/graph/scope-graph.js +1 -0
  53. package/dist/graph/scope-graph.js.map +1 -1
  54. package/dist/languages/plugins/bash.d.ts.map +1 -1
  55. package/dist/languages/plugins/bash.js +17 -0
  56. package/dist/languages/plugins/bash.js.map +1 -1
  57. package/dist/languages/registry.d.ts.map +1 -1
  58. package/dist/languages/registry.js +6 -0
  59. package/dist/languages/registry.js.map +1 -1
  60. package/dist/languages/types.d.ts +1 -1
  61. package/dist/languages/types.d.ts.map +1 -1
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/index.d.ts.map +1 -1
  64. package/dist/wasm/tree-sitter-tsx.wasm +0 -0
  65. package/package.json +2 -1
@@ -4387,7 +4387,7 @@ function detectLanguage(tree) {
4387
4387
  }
4388
4388
  function extractTypes(tree, cache, language) {
4389
4389
  const effectiveLanguage = language ?? detectLanguage(tree);
4390
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
4390
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
4391
4391
  const isPython = effectiveLanguage === "python";
4392
4392
  const isRust = effectiveLanguage === "rust";
4393
4393
  if (effectiveLanguage === "go") {
@@ -5773,7 +5773,7 @@ function detectLanguageFromTree(tree, cache) {
5773
5773
  function extractCalls(tree, cache, language) {
5774
5774
  const calls = [];
5775
5775
  const detectedLanguage = language ?? detectLanguageFromTree(tree, cache);
5776
- const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
5776
+ const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript" || detectedLanguage === "tsx";
5777
5777
  const isPython = detectedLanguage === "python";
5778
5778
  const isRust = detectedLanguage === "rust";
5779
5779
  if (detectedLanguage === "go") {
@@ -5822,8 +5822,137 @@ function extractJavaScriptCalls(tree, cache) {
5822
5822
  calls.push(callInfo);
5823
5823
  }
5824
5824
  }
5825
+ const jsxAttributes = getNodesFromCache(tree.rootNode, "jsx_attribute", cache);
5826
+ for (const attr of jsxAttributes) {
5827
+ const callInfo = extractJSXAttributeSink(attr);
5828
+ if (callInfo) {
5829
+ calls.push(callInfo);
5830
+ }
5831
+ }
5832
+ const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
5833
+ for (const assign of assignments) {
5834
+ const callInfo = extractDomPropertyAssignmentSink(assign);
5835
+ if (callInfo) {
5836
+ calls.push(callInfo);
5837
+ }
5838
+ }
5825
5839
  return calls;
5826
5840
  }
5841
+ var DOM_XSS_ASSIGNMENT_PROPERTIES = /* @__PURE__ */ new Set([
5842
+ "innerHTML",
5843
+ "outerHTML"
5844
+ ]);
5845
+ function extractDomPropertyAssignmentSink(node) {
5846
+ const leftNode = node.childForFieldName("left");
5847
+ const rightNode = node.childForFieldName("right");
5848
+ if (!leftNode || !rightNode) return null;
5849
+ if (leftNode.type !== "member_expression") return null;
5850
+ const propertyNode = leftNode.childForFieldName("property");
5851
+ const objectNode = leftNode.childForFieldName("object");
5852
+ if (!propertyNode) return null;
5853
+ const propertyName = getNodeText(propertyNode);
5854
+ if (!DOM_XSS_ASSIGNMENT_PROPERTIES.has(propertyName)) return null;
5855
+ const receiver = objectNode ? getNodeText(objectNode) : null;
5856
+ const expression = getNodeText(rightNode);
5857
+ const { variable, literal } = analyzeJSArgument(rightNode);
5858
+ const enclosingFunc = findJSEnclosingFunction(node);
5859
+ return {
5860
+ method_name: propertyName,
5861
+ receiver,
5862
+ arguments: [
5863
+ {
5864
+ position: 0,
5865
+ expression,
5866
+ variable,
5867
+ literal
5868
+ }
5869
+ ],
5870
+ location: {
5871
+ line: node.startPosition.row + 1,
5872
+ column: node.startPosition.column
5873
+ },
5874
+ in_method: enclosingFunc,
5875
+ resolved: true,
5876
+ resolution: {
5877
+ status: "resolved",
5878
+ target: `DOM.${propertyName}`
5879
+ }
5880
+ };
5881
+ }
5882
+ function extractJSXAttributeSink(attr) {
5883
+ let nameNode = null;
5884
+ for (let i2 = 0; i2 < attr.childCount; i2++) {
5885
+ const child = attr.child(i2);
5886
+ if (child && child.type === "property_identifier") {
5887
+ nameNode = child;
5888
+ break;
5889
+ }
5890
+ }
5891
+ if (!nameNode) return null;
5892
+ const attrName = getNodeText(nameNode);
5893
+ if (attrName !== "dangerouslySetInnerHTML") return null;
5894
+ let valueExpr = null;
5895
+ for (let i2 = 0; i2 < attr.childCount; i2++) {
5896
+ const child = attr.child(i2);
5897
+ if (child && child.type === "jsx_expression") {
5898
+ valueExpr = child;
5899
+ break;
5900
+ }
5901
+ }
5902
+ if (!valueExpr) return null;
5903
+ let htmlValue = null;
5904
+ for (let i2 = 0; i2 < valueExpr.childCount; i2++) {
5905
+ const inner = valueExpr.child(i2);
5906
+ if (!inner || inner.type !== "object") continue;
5907
+ for (let j = 0; j < inner.childCount; j++) {
5908
+ const pair = inner.child(j);
5909
+ if (!pair || pair.type !== "pair") continue;
5910
+ const keyNode = pair.childForFieldName("key");
5911
+ if (!keyNode) continue;
5912
+ const keyText = getNodeText(keyNode).replace(/^["']|["']$/g, "");
5913
+ if (keyText === "__html") {
5914
+ htmlValue = pair.childForFieldName("value");
5915
+ break;
5916
+ }
5917
+ }
5918
+ if (htmlValue) break;
5919
+ }
5920
+ if (!htmlValue) {
5921
+ for (let i2 = 0; i2 < valueExpr.childCount; i2++) {
5922
+ const inner = valueExpr.child(i2);
5923
+ if (inner && inner.type !== "{" && inner.type !== "}") {
5924
+ htmlValue = inner;
5925
+ break;
5926
+ }
5927
+ }
5928
+ }
5929
+ if (!htmlValue) return null;
5930
+ const expression = getNodeText(htmlValue);
5931
+ const { variable, literal } = analyzeJSArgument(htmlValue);
5932
+ const enclosingFunc = findJSEnclosingFunction(attr);
5933
+ return {
5934
+ method_name: "dangerouslySetInnerHTML",
5935
+ receiver: null,
5936
+ arguments: [
5937
+ {
5938
+ position: 0,
5939
+ expression,
5940
+ variable,
5941
+ literal
5942
+ }
5943
+ ],
5944
+ location: {
5945
+ line: attr.startPosition.row + 1,
5946
+ column: attr.startPosition.column
5947
+ },
5948
+ in_method: enclosingFunc,
5949
+ resolved: true,
5950
+ resolution: {
5951
+ status: "resolved",
5952
+ target: "react.dangerouslySetInnerHTML"
5953
+ }
5954
+ };
5955
+ }
5827
5956
  function buildJSResolutionContext(tree, cache) {
5828
5957
  const context = {
5829
5958
  functionNames: /* @__PURE__ */ new Set(),
@@ -7331,7 +7460,7 @@ function detectLanguage2(tree) {
7331
7460
  }
7332
7461
  function extractImports(tree, language) {
7333
7462
  const effectiveLanguage = language ?? detectLanguage2(tree);
7334
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
7463
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
7335
7464
  const isPython = effectiveLanguage === "python";
7336
7465
  const isRust = effectiveLanguage === "rust";
7337
7466
  if (effectiveLanguage === "go") {
@@ -8023,7 +8152,7 @@ function detectLanguage3(tree) {
8023
8152
  }
8024
8153
  function buildCFG(tree, language) {
8025
8154
  const effectiveLanguage = language ?? detectLanguage3(tree);
8026
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
8155
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
8027
8156
  const allBlocks = [];
8028
8157
  const allEdges = [];
8029
8158
  let blockIdCounter = 0;
@@ -8598,7 +8727,7 @@ function detectLanguage4(tree) {
8598
8727
  }
8599
8728
  function buildDFG(tree, cache, language) {
8600
8729
  const effectiveLanguage = language ?? detectLanguage4(tree);
8601
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
8730
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
8602
8731
  if (isJavaScript) {
8603
8732
  return buildJavaScriptDFG(tree, cache);
8604
8733
  }
@@ -9406,7 +9535,18 @@ function buildBashDFG(tree) {
9406
9535
  if (varNameNode) {
9407
9536
  const varName = getNodeText(varNameNode);
9408
9537
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
9409
- const reachingDef = findReachingDef(varName, scopeStack);
9538
+ let reachingDef = findReachingDef(varName, scopeStack);
9539
+ if (reachingDef === null && !positionalParams.includes(varName)) {
9540
+ const def = {
9541
+ id: defIdCounter++,
9542
+ variable: varName,
9543
+ line: 0,
9544
+ kind: "param"
9545
+ };
9546
+ defs.push(def);
9547
+ scopeStack[0].set(varName, def.id);
9548
+ reachingDef = def.id;
9549
+ }
9410
9550
  uses.push({
9411
9551
  id: useIdCounter++,
9412
9552
  variable: varName,
@@ -9419,7 +9559,18 @@ function buildBashDFG(tree) {
9419
9559
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
9420
9560
  if (varNameNode && varNameNode.type === "variable_name") {
9421
9561
  const varName = getNodeText(varNameNode);
9422
- const reachingDef = findReachingDef(varName, scopeStack);
9562
+ let reachingDef = findReachingDef(varName, scopeStack);
9563
+ if (reachingDef === null && !positionalParams.includes(varName)) {
9564
+ const def = {
9565
+ id: defIdCounter++,
9566
+ variable: varName,
9567
+ line: 0,
9568
+ kind: "param"
9569
+ };
9570
+ defs.push(def);
9571
+ scopeStack[0].set(varName, def.id);
9572
+ reachingDef = def.id;
9573
+ }
9423
9574
  uses.push({
9424
9575
  id: useIdCounter++,
9425
9576
  variable: varName,
@@ -10168,8 +10319,12 @@ var DEFAULT_SOURCES = [
10168
10319
  { method: "get", class: "cookies", type: "http_cookie", severity: "high", return_tainted: true },
10169
10320
  { property: "json", object: "request", type: "http_body", severity: "high", property_tainted: true },
10170
10321
  { property: "data", object: "request", type: "http_body", severity: "high", property_tainted: true },
10322
+ { property: "stream", object: "request", type: "http_body", severity: "high", property_tainted: true },
10171
10323
  { property: "path", object: "request", type: "http_path", severity: "medium", property_tainted: true },
10172
10324
  { property: "query_string", object: "request", type: "http_query", severity: "high", property_tainted: true },
10325
+ // Flask request.get_data() — raw request bytes (method form, parallel to request.data property)
10326
+ { method: "get_data", class: "request", type: "http_body", severity: "high", return_tainted: true },
10327
+ { method: "get_json", class: "request", type: "http_body", severity: "high", return_tainted: true },
10173
10328
  // Django request object
10174
10329
  { method: "get", class: "GET", type: "http_param", severity: "high", return_tainted: true },
10175
10330
  { method: "get", class: "POST", type: "http_param", severity: "high", return_tainted: true },
@@ -10381,14 +10536,19 @@ var DEFAULT_SINKS = [
10381
10536
  { method: "setExecutable", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10382
10537
  { method: "setCommand", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10383
10538
  { method: "execute", class: "Java", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10384
- // Shell/Bash utilities
10385
- { method: "bash", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10386
- { method: "shell", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10387
- { method: "sh", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10388
- { method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10389
- { method: "fork", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10390
- { method: "popen", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10391
- { method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10539
+ // Shell/Bash utilities — these are method-call sinks in host languages
10540
+ // (Java Runtime/ProcessBuilder, JS child_process spawn/exec, Python subprocess, etc.).
10541
+ // When the analyzed file IS a bash/shell script, the bash plugin's per-flag entries
10542
+ // (argPositions: [1] for `bash -c <cmd>`) MUST win. Restrict these generic entries
10543
+ // to non-shell languages so they don't collide on the dedup key
10544
+ // `${location}:${call.location.line}:${pattern.cwe}`.
10545
+ { method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10546
+ { method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10547
+ { method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10548
+ { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10549
+ { method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10550
+ { method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10551
+ { method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10392
10552
  // Apache Commons Exec
10393
10553
  // Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
10394
10554
  { method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10478,6 +10638,12 @@ var DEFAULT_SINKS = [
10478
10638
  { method: "unzip", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10479
10639
  { method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10480
10640
  { method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10641
+ // Python zipfile/tarfile use lowercase extractall (PEP 8 naming)
10642
+ { method: "extractall", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0], languages: ["python"] },
10643
+ // Python zipfile.ZipFile(path) — tainted archive path enables Zip-Slip via malicious archive
10644
+ { method: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
10645
+ // Flask send_from_directory: untrusted filename can escape directory via ../
10646
+ { method: "send_from_directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [1], languages: ["python"] },
10481
10647
  { method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10482
10648
  // Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
10483
10649
  { method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11562,16 +11728,42 @@ var DEFAULT_SINKS = [
11562
11728
  // value position so a tainted variable is detected.
11563
11729
  { method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11564
11730
  { method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11565
- // Mass-assignment (CWE-915) — Sprint 6, #86.
11566
- // JS Object.assign(target, ...sources) — sources are arg 1..N, and if any
11567
- // source is request-tainted, every key gets written onto the target. We
11568
- // flag the source positions; the analyzer only needs one tainted to fire.
11569
- { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11570
- // Lodash bulk-merge helpers behave identically.
11571
- { method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11572
- { method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11731
+ // Mass-assignment (CWE-915 / CWE-1321) — Sprint 6, #86; cognium-dev #68 Sprint 10.
11732
+ // JS Object.assign(target, ...sources), `_.merge`, `_.extend`, `$.extend`,
11733
+ // `Object.defineProperty` when fed an attacker-controlled bag, they write
11734
+ // arbitrary keys onto the target (or, for `__proto__`/`constructor.prototype`,
11735
+ // pollute the prototype chain). The CWE is CWE-1321 (Prototype Pollution),
11736
+ // which subsumes mass assignment for JS sinks operating on plain Objects.
11737
+ // We keep the existing `mass_assignment` SinkType so consumers route the
11738
+ // findings the same way; only the CWE shifts to flag prototype-pollution.
11739
+ { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11740
+ { method: "defineProperty", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2], languages: ["javascript", "typescript"] },
11741
+ { method: "defineProperties", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1], languages: ["javascript", "typescript"] },
11742
+ // Lodash bulk-merge helpers behave identically. `_.merge` and `lodash.merge`
11743
+ // are aliases — match both receivers.
11744
+ { method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11745
+ { method: "merge", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11746
+ { method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11747
+ { method: "extend", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11748
+ { method: "defaultsDeep", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11749
+ { method: "defaultsDeep", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11573
11750
  // jQuery $.extend(target, source) (legacy).
11574
- { method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] }
11751
+ { method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11752
+ { method: "extend", class: "jQuery", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11753
+ // DOM-XSS via property assignment (CWE-79) — cognium-dev #68 Sprint 10.
11754
+ // `el.innerHTML = tainted` / `el.outerHTML = tainted`. The JS call extractor
11755
+ // emits a synthetic CallInfo with method=`innerHTML`/`outerHTML` for each
11756
+ // matching assignment_expression. These classless entries catch them.
11757
+ { method: "innerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11758
+ { method: "outerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11759
+ // node-serialize.unserialize (CWE-502) — cognium-dev #68 Sprint 10.
11760
+ // The node-serialize package evaluates `_$$ND_FUNC$$_` IIFE payloads on
11761
+ // decode, turning untrusted input into RCE. Match both receiver-bound
11762
+ // calls (`serialize.unserialize(x)`) and destructured imports
11763
+ // (`const { unserialize } = require('node-serialize')`).
11764
+ { method: "unserialize", class: "serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11765
+ { method: "unserialize", class: "node-serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11766
+ { method: "unserialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
11575
11767
  ];
11576
11768
  var DEFAULT_SANITIZERS = [
11577
11769
  // SQL Injection - proper parameter binding sanitizes input
@@ -12178,7 +12370,12 @@ function matchesSourcePattern(call, pattern) {
12178
12370
  if (call.receiver_type && call.receiver_type === pattern.class) {
12179
12371
  } else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
12180
12372
  } else if (!call.receiver) {
12181
- return false;
12373
+ const target = call.resolution?.target;
12374
+ const expectedTail = `${pattern.class}.${pattern.method}`;
12375
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {
12376
+ } else {
12377
+ return false;
12378
+ }
12182
12379
  } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
12183
12380
  return false;
12184
12381
  }
@@ -12445,7 +12642,12 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12445
12642
  }
12446
12643
  return false;
12447
12644
  } else if (!call.receiver && !call.receiver_type) {
12448
- return false;
12645
+ const target = call.resolution?.target;
12646
+ const expectedTail = `${pattern.class}.${pattern.method}`;
12647
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {
12648
+ } else {
12649
+ return false;
12650
+ }
12449
12651
  }
12450
12652
  }
12451
12653
  if (!pattern.class && call.receiver) {
@@ -13287,6 +13489,21 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
13287
13489
  });
13288
13490
  }
13289
13491
  }
13492
+ if (source.variable) {
13493
+ const paramDefs = defsByLine.get(0) ?? [];
13494
+ for (const def of paramDefs) {
13495
+ if (def.kind === "param" && def.variable === source.variable) {
13496
+ tainted.push({
13497
+ variable: def.variable,
13498
+ defId: def.id,
13499
+ line: def.line,
13500
+ sourceType: source.type,
13501
+ sourceLine: source.line,
13502
+ confidence: source.confidence
13503
+ });
13504
+ }
13505
+ }
13506
+ }
13290
13507
  }
13291
13508
  return tainted;
13292
13509
  }