circle-ir 3.59.0 → 3.64.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/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +58 -17
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/html/html-merge.d.ts.map +1 -1
- package/dist/analysis/html/html-merge.js +10 -0
- package/dist/analysis/html/html-merge.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +44 -11
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +7 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +112 -15
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/missing-public-doc-pass.d.ts.map +1 -1
- package/dist/analysis/passes/missing-public-doc-pass.js +2 -1
- package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +4 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +222 -10
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-random-pass.js +2 -1
- package/dist/analysis/passes/weak-random-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +83 -7
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +32 -0
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +19 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +526 -67
- package/dist/core/circle-ir-core.cjs +270 -33
- package/dist/core/circle-ir-core.js +270 -33
- package/dist/core/extractors/calls.js +181 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/cfg.js +1 -1
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/dfg.js +29 -3
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/imports.js +1 -1
- package/dist/core/extractors/imports.js.map +1 -1
- package/dist/core/extractors/runtime-registrations.js +1 -1
- package/dist/core/extractors/runtime-registrations.js.map +1 -1
- package/dist/core/extractors/types.js +1 -1
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/core/parser.d.ts +1 -1
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/graph/scope-graph.d.ts.map +1 -1
- package/dist/graph/scope-graph.js +1 -0
- package/dist/graph/scope-graph.js.map +1 -1
- package/dist/languages/plugins/bash.d.ts.map +1 -1
- package/dist/languages/plugins/bash.js +17 -0
- package/dist/languages/plugins/bash.js.map +1 -1
- package/dist/languages/registry.d.ts.map +1 -1
- package/dist/languages/registry.js +6 -0
- package/dist/languages/registry.js.map +1 -1
- package/dist/languages/types.d.ts +1 -1
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +9 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/package.json +2 -1
|
@@ -4321,7 +4321,7 @@ function detectLanguage(tree) {
|
|
|
4321
4321
|
}
|
|
4322
4322
|
function extractTypes(tree, cache, language) {
|
|
4323
4323
|
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
4324
|
-
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
|
|
4324
|
+
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
4325
4325
|
const isPython = effectiveLanguage === "python";
|
|
4326
4326
|
const isRust = effectiveLanguage === "rust";
|
|
4327
4327
|
if (effectiveLanguage === "go") {
|
|
@@ -5707,7 +5707,7 @@ function detectLanguageFromTree(tree, cache) {
|
|
|
5707
5707
|
function extractCalls(tree, cache, language) {
|
|
5708
5708
|
const calls = [];
|
|
5709
5709
|
const detectedLanguage = language ?? detectLanguageFromTree(tree, cache);
|
|
5710
|
-
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5710
|
+
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript" || detectedLanguage === "tsx";
|
|
5711
5711
|
const isPython = detectedLanguage === "python";
|
|
5712
5712
|
const isRust = detectedLanguage === "rust";
|
|
5713
5713
|
if (detectedLanguage === "go") {
|
|
@@ -5756,8 +5756,137 @@ function extractJavaScriptCalls(tree, cache) {
|
|
|
5756
5756
|
calls.push(callInfo);
|
|
5757
5757
|
}
|
|
5758
5758
|
}
|
|
5759
|
+
const jsxAttributes = getNodesFromCache(tree.rootNode, "jsx_attribute", cache);
|
|
5760
|
+
for (const attr of jsxAttributes) {
|
|
5761
|
+
const callInfo = extractJSXAttributeSink(attr);
|
|
5762
|
+
if (callInfo) {
|
|
5763
|
+
calls.push(callInfo);
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
|
|
5767
|
+
for (const assign of assignments) {
|
|
5768
|
+
const callInfo = extractDomPropertyAssignmentSink(assign);
|
|
5769
|
+
if (callInfo) {
|
|
5770
|
+
calls.push(callInfo);
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5759
5773
|
return calls;
|
|
5760
5774
|
}
|
|
5775
|
+
var DOM_XSS_ASSIGNMENT_PROPERTIES = /* @__PURE__ */ new Set([
|
|
5776
|
+
"innerHTML",
|
|
5777
|
+
"outerHTML"
|
|
5778
|
+
]);
|
|
5779
|
+
function extractDomPropertyAssignmentSink(node) {
|
|
5780
|
+
const leftNode = node.childForFieldName("left");
|
|
5781
|
+
const rightNode = node.childForFieldName("right");
|
|
5782
|
+
if (!leftNode || !rightNode) return null;
|
|
5783
|
+
if (leftNode.type !== "member_expression") return null;
|
|
5784
|
+
const propertyNode = leftNode.childForFieldName("property");
|
|
5785
|
+
const objectNode = leftNode.childForFieldName("object");
|
|
5786
|
+
if (!propertyNode) return null;
|
|
5787
|
+
const propertyName = getNodeText(propertyNode);
|
|
5788
|
+
if (!DOM_XSS_ASSIGNMENT_PROPERTIES.has(propertyName)) return null;
|
|
5789
|
+
const receiver = objectNode ? getNodeText(objectNode) : null;
|
|
5790
|
+
const expression = getNodeText(rightNode);
|
|
5791
|
+
const { variable, literal } = analyzeJSArgument(rightNode);
|
|
5792
|
+
const enclosingFunc = findJSEnclosingFunction(node);
|
|
5793
|
+
return {
|
|
5794
|
+
method_name: propertyName,
|
|
5795
|
+
receiver,
|
|
5796
|
+
arguments: [
|
|
5797
|
+
{
|
|
5798
|
+
position: 0,
|
|
5799
|
+
expression,
|
|
5800
|
+
variable,
|
|
5801
|
+
literal
|
|
5802
|
+
}
|
|
5803
|
+
],
|
|
5804
|
+
location: {
|
|
5805
|
+
line: node.startPosition.row + 1,
|
|
5806
|
+
column: node.startPosition.column
|
|
5807
|
+
},
|
|
5808
|
+
in_method: enclosingFunc,
|
|
5809
|
+
resolved: true,
|
|
5810
|
+
resolution: {
|
|
5811
|
+
status: "resolved",
|
|
5812
|
+
target: `DOM.${propertyName}`
|
|
5813
|
+
}
|
|
5814
|
+
};
|
|
5815
|
+
}
|
|
5816
|
+
function extractJSXAttributeSink(attr) {
|
|
5817
|
+
let nameNode = null;
|
|
5818
|
+
for (let i2 = 0; i2 < attr.childCount; i2++) {
|
|
5819
|
+
const child = attr.child(i2);
|
|
5820
|
+
if (child && child.type === "property_identifier") {
|
|
5821
|
+
nameNode = child;
|
|
5822
|
+
break;
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
if (!nameNode) return null;
|
|
5826
|
+
const attrName = getNodeText(nameNode);
|
|
5827
|
+
if (attrName !== "dangerouslySetInnerHTML") return null;
|
|
5828
|
+
let valueExpr = null;
|
|
5829
|
+
for (let i2 = 0; i2 < attr.childCount; i2++) {
|
|
5830
|
+
const child = attr.child(i2);
|
|
5831
|
+
if (child && child.type === "jsx_expression") {
|
|
5832
|
+
valueExpr = child;
|
|
5833
|
+
break;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
if (!valueExpr) return null;
|
|
5837
|
+
let htmlValue = null;
|
|
5838
|
+
for (let i2 = 0; i2 < valueExpr.childCount; i2++) {
|
|
5839
|
+
const inner = valueExpr.child(i2);
|
|
5840
|
+
if (!inner || inner.type !== "object") continue;
|
|
5841
|
+
for (let j = 0; j < inner.childCount; j++) {
|
|
5842
|
+
const pair = inner.child(j);
|
|
5843
|
+
if (!pair || pair.type !== "pair") continue;
|
|
5844
|
+
const keyNode = pair.childForFieldName("key");
|
|
5845
|
+
if (!keyNode) continue;
|
|
5846
|
+
const keyText = getNodeText(keyNode).replace(/^["']|["']$/g, "");
|
|
5847
|
+
if (keyText === "__html") {
|
|
5848
|
+
htmlValue = pair.childForFieldName("value");
|
|
5849
|
+
break;
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
if (htmlValue) break;
|
|
5853
|
+
}
|
|
5854
|
+
if (!htmlValue) {
|
|
5855
|
+
for (let i2 = 0; i2 < valueExpr.childCount; i2++) {
|
|
5856
|
+
const inner = valueExpr.child(i2);
|
|
5857
|
+
if (inner && inner.type !== "{" && inner.type !== "}") {
|
|
5858
|
+
htmlValue = inner;
|
|
5859
|
+
break;
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
if (!htmlValue) return null;
|
|
5864
|
+
const expression = getNodeText(htmlValue);
|
|
5865
|
+
const { variable, literal } = analyzeJSArgument(htmlValue);
|
|
5866
|
+
const enclosingFunc = findJSEnclosingFunction(attr);
|
|
5867
|
+
return {
|
|
5868
|
+
method_name: "dangerouslySetInnerHTML",
|
|
5869
|
+
receiver: null,
|
|
5870
|
+
arguments: [
|
|
5871
|
+
{
|
|
5872
|
+
position: 0,
|
|
5873
|
+
expression,
|
|
5874
|
+
variable,
|
|
5875
|
+
literal
|
|
5876
|
+
}
|
|
5877
|
+
],
|
|
5878
|
+
location: {
|
|
5879
|
+
line: attr.startPosition.row + 1,
|
|
5880
|
+
column: attr.startPosition.column
|
|
5881
|
+
},
|
|
5882
|
+
in_method: enclosingFunc,
|
|
5883
|
+
resolved: true,
|
|
5884
|
+
resolution: {
|
|
5885
|
+
status: "resolved",
|
|
5886
|
+
target: "react.dangerouslySetInnerHTML"
|
|
5887
|
+
}
|
|
5888
|
+
};
|
|
5889
|
+
}
|
|
5761
5890
|
function buildJSResolutionContext(tree, cache) {
|
|
5762
5891
|
const context = {
|
|
5763
5892
|
functionNames: /* @__PURE__ */ new Set(),
|
|
@@ -7265,7 +7394,7 @@ function detectLanguage2(tree) {
|
|
|
7265
7394
|
}
|
|
7266
7395
|
function extractImports(tree, language) {
|
|
7267
7396
|
const effectiveLanguage = language ?? detectLanguage2(tree);
|
|
7268
|
-
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
|
|
7397
|
+
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
7269
7398
|
const isPython = effectiveLanguage === "python";
|
|
7270
7399
|
const isRust = effectiveLanguage === "rust";
|
|
7271
7400
|
if (effectiveLanguage === "go") {
|
|
@@ -7957,7 +8086,7 @@ function detectLanguage3(tree) {
|
|
|
7957
8086
|
}
|
|
7958
8087
|
function buildCFG(tree, language) {
|
|
7959
8088
|
const effectiveLanguage = language ?? detectLanguage3(tree);
|
|
7960
|
-
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
|
|
8089
|
+
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
7961
8090
|
const allBlocks = [];
|
|
7962
8091
|
const allEdges = [];
|
|
7963
8092
|
let blockIdCounter = 0;
|
|
@@ -8532,7 +8661,7 @@ function detectLanguage4(tree) {
|
|
|
8532
8661
|
}
|
|
8533
8662
|
function buildDFG(tree, cache, language) {
|
|
8534
8663
|
const effectiveLanguage = language ?? detectLanguage4(tree);
|
|
8535
|
-
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
|
|
8664
|
+
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
8536
8665
|
if (isJavaScript) {
|
|
8537
8666
|
return buildJavaScriptDFG(tree, cache);
|
|
8538
8667
|
}
|
|
@@ -9340,7 +9469,18 @@ function buildBashDFG(tree) {
|
|
|
9340
9469
|
if (varNameNode) {
|
|
9341
9470
|
const varName = getNodeText(varNameNode);
|
|
9342
9471
|
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
9343
|
-
|
|
9472
|
+
let reachingDef = findReachingDef(varName, scopeStack);
|
|
9473
|
+
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
9474
|
+
const def = {
|
|
9475
|
+
id: defIdCounter++,
|
|
9476
|
+
variable: varName,
|
|
9477
|
+
line: 0,
|
|
9478
|
+
kind: "param"
|
|
9479
|
+
};
|
|
9480
|
+
defs.push(def);
|
|
9481
|
+
scopeStack[0].set(varName, def.id);
|
|
9482
|
+
reachingDef = def.id;
|
|
9483
|
+
}
|
|
9344
9484
|
uses.push({
|
|
9345
9485
|
id: useIdCounter++,
|
|
9346
9486
|
variable: varName,
|
|
@@ -9353,7 +9493,18 @@ function buildBashDFG(tree) {
|
|
|
9353
9493
|
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
9354
9494
|
if (varNameNode && varNameNode.type === "variable_name") {
|
|
9355
9495
|
const varName = getNodeText(varNameNode);
|
|
9356
|
-
|
|
9496
|
+
let reachingDef = findReachingDef(varName, scopeStack);
|
|
9497
|
+
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
9498
|
+
const def = {
|
|
9499
|
+
id: defIdCounter++,
|
|
9500
|
+
variable: varName,
|
|
9501
|
+
line: 0,
|
|
9502
|
+
kind: "param"
|
|
9503
|
+
};
|
|
9504
|
+
defs.push(def);
|
|
9505
|
+
scopeStack[0].set(varName, def.id);
|
|
9506
|
+
reachingDef = def.id;
|
|
9507
|
+
}
|
|
9357
9508
|
uses.push({
|
|
9358
9509
|
id: useIdCounter++,
|
|
9359
9510
|
variable: varName,
|
|
@@ -10102,8 +10253,12 @@ var DEFAULT_SOURCES = [
|
|
|
10102
10253
|
{ method: "get", class: "cookies", type: "http_cookie", severity: "high", return_tainted: true },
|
|
10103
10254
|
{ property: "json", object: "request", type: "http_body", severity: "high", property_tainted: true },
|
|
10104
10255
|
{ property: "data", object: "request", type: "http_body", severity: "high", property_tainted: true },
|
|
10256
|
+
{ property: "stream", object: "request", type: "http_body", severity: "high", property_tainted: true },
|
|
10105
10257
|
{ property: "path", object: "request", type: "http_path", severity: "medium", property_tainted: true },
|
|
10106
10258
|
{ property: "query_string", object: "request", type: "http_query", severity: "high", property_tainted: true },
|
|
10259
|
+
// Flask request.get_data() — raw request bytes (method form, parallel to request.data property)
|
|
10260
|
+
{ method: "get_data", class: "request", type: "http_body", severity: "high", return_tainted: true },
|
|
10261
|
+
{ method: "get_json", class: "request", type: "http_body", severity: "high", return_tainted: true },
|
|
10107
10262
|
// Django request object
|
|
10108
10263
|
{ method: "get", class: "GET", type: "http_param", severity: "high", return_tainted: true },
|
|
10109
10264
|
{ method: "get", class: "POST", type: "http_param", severity: "high", return_tainted: true },
|
|
@@ -10315,14 +10470,19 @@ var DEFAULT_SINKS = [
|
|
|
10315
10470
|
{ method: "setExecutable", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10316
10471
|
{ method: "setCommand", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10317
10472
|
{ method: "execute", class: "Java", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10318
|
-
// Shell/Bash utilities
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
{
|
|
10324
|
-
{ method: "
|
|
10325
|
-
{ method: "
|
|
10473
|
+
// Shell/Bash utilities — these are method-call sinks in host languages
|
|
10474
|
+
// (Java Runtime/ProcessBuilder, JS child_process spawn/exec, Python subprocess, etc.).
|
|
10475
|
+
// When the analyzed file IS a bash/shell script, the bash plugin's per-flag entries
|
|
10476
|
+
// (argPositions: [1] for `bash -c <cmd>`) MUST win. Restrict these generic entries
|
|
10477
|
+
// to non-shell languages so they don't collide on the dedup key
|
|
10478
|
+
// `${location}:${call.location.line}:${pattern.cwe}`.
|
|
10479
|
+
{ method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10480
|
+
{ method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10481
|
+
{ method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10482
|
+
{ method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10483
|
+
{ method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10484
|
+
{ method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10485
|
+
{ method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10326
10486
|
// Apache Commons Exec
|
|
10327
10487
|
// Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
|
|
10328
10488
|
{ method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -10412,6 +10572,12 @@ var DEFAULT_SINKS = [
|
|
|
10412
10572
|
{ method: "unzip", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
10413
10573
|
{ method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
10414
10574
|
{ method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
10575
|
+
// Python zipfile/tarfile use lowercase extractall (PEP 8 naming)
|
|
10576
|
+
{ method: "extractall", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10577
|
+
// Python zipfile.ZipFile(path) — tainted archive path enables Zip-Slip via malicious archive
|
|
10578
|
+
{ method: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10579
|
+
// Flask send_from_directory: untrusted filename can escape directory via ../
|
|
10580
|
+
{ method: "send_from_directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
10415
10581
|
{ method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
10416
10582
|
// Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
|
|
10417
10583
|
{ method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -11496,16 +11662,42 @@ var DEFAULT_SINKS = [
|
|
|
11496
11662
|
// value position so a tainted variable is detected.
|
|
11497
11663
|
{ method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11498
11664
|
{ method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11499
|
-
// Mass-assignment (CWE-915) — Sprint 6, #86.
|
|
11500
|
-
// JS Object.assign(target, ...sources)
|
|
11501
|
-
//
|
|
11502
|
-
//
|
|
11503
|
-
|
|
11504
|
-
//
|
|
11505
|
-
|
|
11506
|
-
|
|
11665
|
+
// Mass-assignment (CWE-915 / CWE-1321) — Sprint 6, #86; cognium-dev #68 Sprint 10.
|
|
11666
|
+
// JS Object.assign(target, ...sources), `_.merge`, `_.extend`, `$.extend`,
|
|
11667
|
+
// `Object.defineProperty` — when fed an attacker-controlled bag, they write
|
|
11668
|
+
// arbitrary keys onto the target (or, for `__proto__`/`constructor.prototype`,
|
|
11669
|
+
// pollute the prototype chain). The CWE is CWE-1321 (Prototype Pollution),
|
|
11670
|
+
// which subsumes mass assignment for JS sinks operating on plain Objects.
|
|
11671
|
+
// We keep the existing `mass_assignment` SinkType so consumers route the
|
|
11672
|
+
// findings the same way; only the CWE shifts to flag prototype-pollution.
|
|
11673
|
+
{ method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11674
|
+
{ method: "defineProperty", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2], languages: ["javascript", "typescript"] },
|
|
11675
|
+
{ method: "defineProperties", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1], languages: ["javascript", "typescript"] },
|
|
11676
|
+
// Lodash bulk-merge helpers behave identically. `_.merge` and `lodash.merge`
|
|
11677
|
+
// are aliases — match both receivers.
|
|
11678
|
+
{ method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11679
|
+
{ method: "merge", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11680
|
+
{ method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11681
|
+
{ method: "extend", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11682
|
+
{ method: "defaultsDeep", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11683
|
+
{ method: "defaultsDeep", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11507
11684
|
// jQuery $.extend(target, source) (legacy).
|
|
11508
|
-
{ method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-
|
|
11685
|
+
{ method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11686
|
+
{ method: "extend", class: "jQuery", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
|
|
11687
|
+
// DOM-XSS via property assignment (CWE-79) — cognium-dev #68 Sprint 10.
|
|
11688
|
+
// `el.innerHTML = tainted` / `el.outerHTML = tainted`. The JS call extractor
|
|
11689
|
+
// emits a synthetic CallInfo with method=`innerHTML`/`outerHTML` for each
|
|
11690
|
+
// matching assignment_expression. These classless entries catch them.
|
|
11691
|
+
{ method: "innerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11692
|
+
{ method: "outerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11693
|
+
// node-serialize.unserialize (CWE-502) — cognium-dev #68 Sprint 10.
|
|
11694
|
+
// The node-serialize package evaluates `_$$ND_FUNC$$_` IIFE payloads on
|
|
11695
|
+
// decode, turning untrusted input into RCE. Match both receiver-bound
|
|
11696
|
+
// calls (`serialize.unserialize(x)`) and destructured imports
|
|
11697
|
+
// (`const { unserialize } = require('node-serialize')`).
|
|
11698
|
+
{ method: "unserialize", class: "serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11699
|
+
{ method: "unserialize", class: "node-serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
11700
|
+
{ method: "unserialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
|
|
11509
11701
|
];
|
|
11510
11702
|
var DEFAULT_SANITIZERS = [
|
|
11511
11703
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -11780,7 +11972,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11780
11972
|
location: formatCallLocation(call),
|
|
11781
11973
|
severity: pattern.severity,
|
|
11782
11974
|
line: call.location.line,
|
|
11783
|
-
confidence: 1
|
|
11975
|
+
confidence: 1,
|
|
11976
|
+
in_method: call.in_method ?? void 0
|
|
11784
11977
|
});
|
|
11785
11978
|
}
|
|
11786
11979
|
}
|
|
@@ -11797,7 +11990,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11797
11990
|
location: `@${pattern.annotation} ${param.name} in ${method.name}`,
|
|
11798
11991
|
severity: pattern.severity,
|
|
11799
11992
|
line: paramLine,
|
|
11800
|
-
confidence: 1
|
|
11993
|
+
confidence: 1,
|
|
11994
|
+
in_method: method.name
|
|
11801
11995
|
});
|
|
11802
11996
|
}
|
|
11803
11997
|
}
|
|
@@ -11817,7 +12011,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11817
12011
|
location: `@${pattern.method_annotation} ${param.name} in ${method.name}`,
|
|
11818
12012
|
severity: pattern.severity,
|
|
11819
12013
|
line: paramLine,
|
|
11820
|
-
confidence: 1
|
|
12014
|
+
confidence: 1,
|
|
12015
|
+
in_method: method.name
|
|
11821
12016
|
});
|
|
11822
12017
|
}
|
|
11823
12018
|
}
|
|
@@ -11844,7 +12039,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11844
12039
|
severity: "high",
|
|
11845
12040
|
line: paramLine,
|
|
11846
12041
|
confidence: 1,
|
|
11847
|
-
variable: param.name
|
|
12042
|
+
variable: param.name,
|
|
12043
|
+
in_method: method.name
|
|
11848
12044
|
});
|
|
11849
12045
|
}
|
|
11850
12046
|
}
|
|
@@ -11863,8 +12059,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11863
12059
|
location: `${param.type || "any"} ${param.name} in ${method.name}`,
|
|
11864
12060
|
severity: "medium",
|
|
11865
12061
|
line: paramLine,
|
|
11866
|
-
confidence: param.type ? 0.7 : 0.5
|
|
12062
|
+
confidence: param.type ? 0.7 : 0.5,
|
|
11867
12063
|
// Lower confidence for untyped params
|
|
12064
|
+
in_method: method.name
|
|
11868
12065
|
});
|
|
11869
12066
|
}
|
|
11870
12067
|
}
|
|
@@ -11884,7 +12081,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11884
12081
|
location: `${arg.expression} in ${call.in_method || "anonymous"}`,
|
|
11885
12082
|
severity: "high",
|
|
11886
12083
|
line: call.location.line,
|
|
11887
|
-
confidence: 1
|
|
12084
|
+
confidence: 1,
|
|
12085
|
+
in_method: call.in_method ?? void 0
|
|
11888
12086
|
});
|
|
11889
12087
|
}
|
|
11890
12088
|
}
|
|
@@ -11905,7 +12103,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11905
12103
|
location: `${arg.expression} in ${call.in_method || "anonymous"}`,
|
|
11906
12104
|
severity: "high",
|
|
11907
12105
|
line: call.location.line,
|
|
11908
|
-
confidence: 1
|
|
12106
|
+
confidence: 1,
|
|
12107
|
+
in_method: call.in_method ?? void 0
|
|
11909
12108
|
});
|
|
11910
12109
|
}
|
|
11911
12110
|
break;
|
|
@@ -11936,6 +12135,18 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
11936
12135
|
if (m) s.variable = m[1];
|
|
11937
12136
|
}
|
|
11938
12137
|
}
|
|
12138
|
+
if (language === "java" && sourceLines) {
|
|
12139
|
+
const JAVA_ASSIGN_LHS = /^\s*(?:(?:final|public|private|protected|static|synchronized|volatile|transient)\s+)*(?:[A-Za-z_][\w.]*(?:\s*<[^=]*>)?(?:\s*\[\s*\])*\s+)?([A-Za-z_]\w*)\s*=(?!=)/;
|
|
12140
|
+
for (const s of result) {
|
|
12141
|
+
if (s.variable && s.variable.length > 0) continue;
|
|
12142
|
+
const lineText = sourceLines[s.line - 1] ?? "";
|
|
12143
|
+
const m = JAVA_ASSIGN_LHS.exec(lineText);
|
|
12144
|
+
if (!m) continue;
|
|
12145
|
+
const rhs = lineText.slice(m[0].length).trimStart();
|
|
12146
|
+
if (/^new\b/.test(rhs)) continue;
|
|
12147
|
+
s.variable = m[1];
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
11939
12150
|
return result;
|
|
11940
12151
|
}
|
|
11941
12152
|
function isInterproceduralTaintableType(typeName) {
|
|
@@ -12112,7 +12323,12 @@ function matchesSourcePattern(call, pattern) {
|
|
|
12112
12323
|
if (call.receiver_type && call.receiver_type === pattern.class) {
|
|
12113
12324
|
} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {
|
|
12114
12325
|
} else if (!call.receiver) {
|
|
12115
|
-
|
|
12326
|
+
const target = call.resolution?.target;
|
|
12327
|
+
const expectedTail = `${pattern.class}.${pattern.method}`;
|
|
12328
|
+
if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {
|
|
12329
|
+
} else {
|
|
12330
|
+
return false;
|
|
12331
|
+
}
|
|
12116
12332
|
} else if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
12117
12333
|
return false;
|
|
12118
12334
|
}
|
|
@@ -12379,7 +12595,12 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
12379
12595
|
}
|
|
12380
12596
|
return false;
|
|
12381
12597
|
} else if (!call.receiver && !call.receiver_type) {
|
|
12382
|
-
|
|
12598
|
+
const target = call.resolution?.target;
|
|
12599
|
+
const expectedTail = `${pattern.class}.${pattern.method}`;
|
|
12600
|
+
if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {
|
|
12601
|
+
} else {
|
|
12602
|
+
return false;
|
|
12603
|
+
}
|
|
12383
12604
|
}
|
|
12384
12605
|
}
|
|
12385
12606
|
if (!pattern.class && call.receiver) {
|
|
@@ -13210,6 +13431,7 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
13210
13431
|
for (const def of defsNextLine) {
|
|
13211
13432
|
const callsOnSourceLine = callsByLine.get(source.line) ?? [];
|
|
13212
13433
|
if (callsOnSourceLine.length > 0) {
|
|
13434
|
+
if (source.variable && def.variable !== source.variable) continue;
|
|
13213
13435
|
tainted.push({
|
|
13214
13436
|
variable: def.variable,
|
|
13215
13437
|
defId: def.id,
|
|
@@ -13221,6 +13443,21 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
13221
13443
|
});
|
|
13222
13444
|
}
|
|
13223
13445
|
}
|
|
13446
|
+
if (source.variable) {
|
|
13447
|
+
const paramDefs = defsByLine.get(0) ?? [];
|
|
13448
|
+
for (const def of paramDefs) {
|
|
13449
|
+
if (def.kind === "param" && def.variable === source.variable) {
|
|
13450
|
+
tainted.push({
|
|
13451
|
+
variable: def.variable,
|
|
13452
|
+
defId: def.id,
|
|
13453
|
+
line: def.line,
|
|
13454
|
+
sourceType: source.type,
|
|
13455
|
+
sourceLine: source.line,
|
|
13456
|
+
confidence: source.confidence
|
|
13457
|
+
});
|
|
13458
|
+
}
|
|
13459
|
+
}
|
|
13460
|
+
}
|
|
13224
13461
|
}
|
|
13225
13462
|
return tainted;
|
|
13226
13463
|
}
|