circle-ir 4.9.10 → 4.9.12
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/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +16 -1
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +46 -0
- 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 +76 -4
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts +0 -8
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +115 -4
- 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 +15 -0
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/browser/circle-ir.js +110 -8
- package/dist/core/circle-ir-core.cjs +37 -3
- package/dist/core/circle-ir-core.js +37 -3
- package/package.json +1 -1
|
@@ -15056,14 +15056,30 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
15056
15056
|
// regex registry so it's recognized in subscript/var contexts.
|
|
15057
15057
|
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
15058
15058
|
];
|
|
15059
|
+
function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
15060
|
+
const zeroArgCtors = /* @__PURE__ */ new Set();
|
|
15061
|
+
for (const call of calls) {
|
|
15062
|
+
if (call.is_constructor && call.arguments.length === 0) {
|
|
15063
|
+
zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
|
|
15064
|
+
}
|
|
15065
|
+
}
|
|
15066
|
+
return sinks.filter((sink) => {
|
|
15067
|
+
if (sink.type !== "sql_injection") return true;
|
|
15068
|
+
if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
|
|
15069
|
+
return false;
|
|
15070
|
+
}
|
|
15071
|
+
return true;
|
|
15072
|
+
});
|
|
15073
|
+
}
|
|
15059
15074
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
15060
15075
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
15061
15076
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
15062
15077
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
15063
15078
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
15064
15079
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
15080
|
+
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
15065
15081
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
15066
|
-
return { sources, sinks, sanitizers };
|
|
15082
|
+
return { sources, sinks: gatedSinks, sanitizers };
|
|
15067
15083
|
}
|
|
15068
15084
|
function sinkPatternAppliesTo(pattern, language) {
|
|
15069
15085
|
if (language === void 0) return true;
|
|
@@ -15334,7 +15350,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
15334
15350
|
}
|
|
15335
15351
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
15336
15352
|
for (const source of sources) {
|
|
15337
|
-
const key = `${source.line}:${source.type}`;
|
|
15353
|
+
const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
|
|
15338
15354
|
const existing = sourceMap.get(key);
|
|
15339
15355
|
if (!existing || source.confidence > existing.confidence) {
|
|
15340
15356
|
sourceMap.set(key, source);
|
|
@@ -15834,7 +15850,7 @@ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)
|
|
|
15834
15850
|
const guards = guardMap.get(cand);
|
|
15835
15851
|
if (!guards) continue;
|
|
15836
15852
|
for (const g of guards) {
|
|
15837
|
-
if (g.i
|
|
15853
|
+
if (g.i > sinkIdx || checked.has(g)) continue;
|
|
15838
15854
|
checked.add(g);
|
|
15839
15855
|
const block = csThenBlock(sourceLines, g.i, g.rest);
|
|
15840
15856
|
if (g.op === "==") {
|
|
@@ -15952,6 +15968,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
15952
15968
|
// hutool
|
|
15953
15969
|
"RuntimeUtil"
|
|
15954
15970
|
]);
|
|
15971
|
+
function isRegexReceiver(receiver, sourceLines) {
|
|
15972
|
+
const r = (receiver ?? "").trim();
|
|
15973
|
+
if (r.length === 0) return false;
|
|
15974
|
+
if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
|
|
15975
|
+
if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
|
|
15976
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
|
|
15977
|
+
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
15978
|
+
return sourceLines.some((l) => decl.test(l));
|
|
15979
|
+
}
|
|
15955
15980
|
function isFunctionCallbackArgument(arg) {
|
|
15956
15981
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
15957
15982
|
const expr = (arg.expression ?? "").trim();
|
|
@@ -16134,6 +16159,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
16134
16159
|
continue;
|
|
16135
16160
|
}
|
|
16136
16161
|
}
|
|
16162
|
+
if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
16163
|
+
continue;
|
|
16164
|
+
}
|
|
16165
|
+
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
16166
|
+
continue;
|
|
16167
|
+
}
|
|
16137
16168
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
16138
16169
|
continue;
|
|
16139
16170
|
}
|
|
@@ -18784,6 +18815,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
18784
18815
|
for (const source of sources) {
|
|
18785
18816
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
18786
18817
|
for (const def of defsOnLine) {
|
|
18818
|
+
if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
18819
|
+
continue;
|
|
18820
|
+
}
|
|
18787
18821
|
tainted.push({
|
|
18788
18822
|
variable: def.variable,
|
|
18789
18823
|
defId: def.id,
|
|
@@ -34110,6 +34144,21 @@ function hasJavaImportFromPackage(packagePrefix, sourceLines) {
|
|
|
34110
34144
|
}
|
|
34111
34145
|
return false;
|
|
34112
34146
|
}
|
|
34147
|
+
function hasSameOriginLiteralPrefix(lineText) {
|
|
34148
|
+
const m = lineText.match(/\(\s*(['"`])([^'"`]*)\1\s*(?:\+|,|\))/);
|
|
34149
|
+
const prefix = m?.[2];
|
|
34150
|
+
if (prefix === void 0) {
|
|
34151
|
+
const t = lineText.match(/\(\s*`([^`$]*)\$\{/);
|
|
34152
|
+
if (!t) return false;
|
|
34153
|
+
return isSameOriginQueryPrefix(t[1]);
|
|
34154
|
+
}
|
|
34155
|
+
return isSameOriginQueryPrefix(prefix);
|
|
34156
|
+
}
|
|
34157
|
+
function isSameOriginQueryPrefix(prefix) {
|
|
34158
|
+
if (!prefix.startsWith("/")) return false;
|
|
34159
|
+
if (prefix.startsWith("//")) return false;
|
|
34160
|
+
return prefix.includes("?");
|
|
34161
|
+
}
|
|
34113
34162
|
var SinkFilterPass = class {
|
|
34114
34163
|
name = "sink-filter";
|
|
34115
34164
|
category = "security";
|
|
@@ -34265,6 +34314,9 @@ var SinkFilterPass = class {
|
|
|
34265
34314
|
if (setHeaderMatch) {
|
|
34266
34315
|
return false;
|
|
34267
34316
|
}
|
|
34317
|
+
if (sink.type === "open_redirect" && hasSameOriginLiteralPrefix(sinkLineText)) {
|
|
34318
|
+
return false;
|
|
34319
|
+
}
|
|
34268
34320
|
if (sink.method === "cookie" && sink.type === "crlf") {
|
|
34269
34321
|
return false;
|
|
34270
34322
|
}
|
|
@@ -36332,7 +36384,7 @@ var TaintPropagationPass = class {
|
|
|
36332
36384
|
for (const f of paramFlows) {
|
|
36333
36385
|
pushIfNew(f);
|
|
36334
36386
|
}
|
|
36335
|
-
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
|
|
36387
|
+
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language, types) ?? [];
|
|
36336
36388
|
for (const f of exprScanFlows) {
|
|
36337
36389
|
if (flowKeys.has(flowKey(f))) continue;
|
|
36338
36390
|
const flowForCheck = {
|
|
@@ -36484,6 +36536,36 @@ var TaintPropagationPass = class {
|
|
|
36484
36536
|
return bestByKey.get(key) === f;
|
|
36485
36537
|
});
|
|
36486
36538
|
}
|
|
36539
|
+
if (ctx.language === "csharp" && finalFlows.length > 1) {
|
|
36540
|
+
const CSHARP_ADO_EXECUTE = /^Execute(?:Reader|NonQuery|Scalar)(?:Async)?$/;
|
|
36541
|
+
const commandTextReceiverByLine = /* @__PURE__ */ new Map();
|
|
36542
|
+
const execLinesByReceiver = /* @__PURE__ */ new Map();
|
|
36543
|
+
for (const call of calls) {
|
|
36544
|
+
const receiver = (call.receiver ?? "").trim();
|
|
36545
|
+
if (!receiver) continue;
|
|
36546
|
+
if (call.method_name === "CommandText") {
|
|
36547
|
+
commandTextReceiverByLine.set(call.location.line, receiver);
|
|
36548
|
+
} else if (CSHARP_ADO_EXECUTE.test(call.method_name)) {
|
|
36549
|
+
const lines = execLinesByReceiver.get(receiver) ?? [];
|
|
36550
|
+
lines.push(call.location.line);
|
|
36551
|
+
execLinesByReceiver.set(receiver, lines);
|
|
36552
|
+
}
|
|
36553
|
+
}
|
|
36554
|
+
if (commandTextReceiverByLine.size > 0) {
|
|
36555
|
+
const reportedSqlSinkLines = new Set(
|
|
36556
|
+
finalFlows.filter((f) => f.sink_type === "sql_injection").map((f) => f.sink_line)
|
|
36557
|
+
);
|
|
36558
|
+
finalFlows = finalFlows.filter((f) => {
|
|
36559
|
+
if (f.sink_type !== "sql_injection") return true;
|
|
36560
|
+
const receiver = commandTextReceiverByLine.get(f.sink_line);
|
|
36561
|
+
if (receiver === void 0) return true;
|
|
36562
|
+
const execLines = execLinesByReceiver.get(receiver) ?? [];
|
|
36563
|
+
return !execLines.some(
|
|
36564
|
+
(line) => line > f.sink_line && reportedSqlSinkLines.has(line)
|
|
36565
|
+
);
|
|
36566
|
+
});
|
|
36567
|
+
}
|
|
36568
|
+
}
|
|
36487
36569
|
return { flows: finalFlows };
|
|
36488
36570
|
}
|
|
36489
36571
|
};
|
|
@@ -36901,7 +36983,7 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
|
36901
36983
|
}
|
|
36902
36984
|
return false;
|
|
36903
36985
|
}
|
|
36904
|
-
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
|
|
36986
|
+
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
|
|
36905
36987
|
const flows = [];
|
|
36906
36988
|
const sourcesWithVar = sources.filter(
|
|
36907
36989
|
(s) => typeof s.variable === "string" && s.variable.length > 0
|
|
@@ -37158,12 +37240,32 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
37158
37240
|
for (const s of sourcesWithVar) {
|
|
37159
37241
|
if (s.line < anchor.line) anchor = s;
|
|
37160
37242
|
}
|
|
37243
|
+
const methodAt = (line) => {
|
|
37244
|
+
for (const t of types ?? []) {
|
|
37245
|
+
for (const m of t.methods) {
|
|
37246
|
+
if (line >= m.start_line && line <= m.end_line) return m.name;
|
|
37247
|
+
}
|
|
37248
|
+
}
|
|
37249
|
+
return null;
|
|
37250
|
+
};
|
|
37161
37251
|
const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
|
|
37162
|
-
for (const [varName] of derived) {
|
|
37252
|
+
for (const [varName, derivedLine] of derived) {
|
|
37163
37253
|
if (!varName || existingVars.has(varName)) continue;
|
|
37254
|
+
const owner = methodAt(derivedLine);
|
|
37255
|
+
let scopedAnchor = anchor;
|
|
37256
|
+
if (owner) {
|
|
37257
|
+
let best;
|
|
37258
|
+
for (const s of sourcesWithVar) {
|
|
37259
|
+
const sOwner = s.in_method ?? methodAt(s.line);
|
|
37260
|
+
if (sOwner !== owner) continue;
|
|
37261
|
+
if (!best || s.line < best.line) best = s;
|
|
37262
|
+
}
|
|
37263
|
+
if (best) scopedAnchor = best;
|
|
37264
|
+
}
|
|
37164
37265
|
sourcesWithVar.push({
|
|
37165
|
-
...
|
|
37166
|
-
variable: varName
|
|
37266
|
+
...scopedAnchor,
|
|
37267
|
+
variable: varName,
|
|
37268
|
+
...owner ? { in_method: owner } : {}
|
|
37167
37269
|
});
|
|
37168
37270
|
existingVars.add(varName);
|
|
37169
37271
|
}
|
|
@@ -14356,14 +14356,30 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
14356
14356
|
// regex registry so it's recognized in subscript/var contexts.
|
|
14357
14357
|
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
14358
14358
|
];
|
|
14359
|
+
function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
14360
|
+
const zeroArgCtors = /* @__PURE__ */ new Set();
|
|
14361
|
+
for (const call of calls) {
|
|
14362
|
+
if (call.is_constructor && call.arguments.length === 0) {
|
|
14363
|
+
zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
|
|
14364
|
+
}
|
|
14365
|
+
}
|
|
14366
|
+
return sinks.filter((sink) => {
|
|
14367
|
+
if (sink.type !== "sql_injection") return true;
|
|
14368
|
+
if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
|
|
14369
|
+
return false;
|
|
14370
|
+
}
|
|
14371
|
+
return true;
|
|
14372
|
+
});
|
|
14373
|
+
}
|
|
14359
14374
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
14360
14375
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
14361
14376
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
14362
14377
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
14363
14378
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14364
14379
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14380
|
+
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14365
14381
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
14366
|
-
return { sources, sinks, sanitizers };
|
|
14382
|
+
return { sources, sinks: gatedSinks, sanitizers };
|
|
14367
14383
|
}
|
|
14368
14384
|
function sinkPatternAppliesTo(pattern, language) {
|
|
14369
14385
|
if (language === void 0) return true;
|
|
@@ -14634,7 +14650,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14634
14650
|
}
|
|
14635
14651
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
14636
14652
|
for (const source of sources) {
|
|
14637
|
-
const key = `${source.line}:${source.type}`;
|
|
14653
|
+
const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
|
|
14638
14654
|
const existing = sourceMap.get(key);
|
|
14639
14655
|
if (!existing || source.confidence > existing.confidence) {
|
|
14640
14656
|
sourceMap.set(key, source);
|
|
@@ -15134,7 +15150,7 @@ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)
|
|
|
15134
15150
|
const guards = guardMap.get(cand);
|
|
15135
15151
|
if (!guards) continue;
|
|
15136
15152
|
for (const g of guards) {
|
|
15137
|
-
if (g.i
|
|
15153
|
+
if (g.i > sinkIdx || checked.has(g)) continue;
|
|
15138
15154
|
checked.add(g);
|
|
15139
15155
|
const block = csThenBlock(sourceLines, g.i, g.rest);
|
|
15140
15156
|
if (g.op === "==") {
|
|
@@ -15252,6 +15268,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
15252
15268
|
// hutool
|
|
15253
15269
|
"RuntimeUtil"
|
|
15254
15270
|
]);
|
|
15271
|
+
function isRegexReceiver(receiver, sourceLines) {
|
|
15272
|
+
const r = (receiver ?? "").trim();
|
|
15273
|
+
if (r.length === 0) return false;
|
|
15274
|
+
if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
|
|
15275
|
+
if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
|
|
15276
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
|
|
15277
|
+
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
15278
|
+
return sourceLines.some((l) => decl.test(l));
|
|
15279
|
+
}
|
|
15255
15280
|
function isFunctionCallbackArgument(arg) {
|
|
15256
15281
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
15257
15282
|
const expr = (arg.expression ?? "").trim();
|
|
@@ -15434,6 +15459,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15434
15459
|
continue;
|
|
15435
15460
|
}
|
|
15436
15461
|
}
|
|
15462
|
+
if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15463
|
+
continue;
|
|
15464
|
+
}
|
|
15465
|
+
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
15466
|
+
continue;
|
|
15467
|
+
}
|
|
15437
15468
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
15438
15469
|
continue;
|
|
15439
15470
|
}
|
|
@@ -16749,6 +16780,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
16749
16780
|
for (const source of sources) {
|
|
16750
16781
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
16751
16782
|
for (const def of defsOnLine) {
|
|
16783
|
+
if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
16784
|
+
continue;
|
|
16785
|
+
}
|
|
16752
16786
|
tainted.push({
|
|
16753
16787
|
variable: def.variable,
|
|
16754
16788
|
defId: def.id,
|
|
@@ -14290,14 +14290,30 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
14290
14290
|
// regex registry so it's recognized in subscript/var contexts.
|
|
14291
14291
|
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
14292
14292
|
];
|
|
14293
|
+
function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
14294
|
+
const zeroArgCtors = /* @__PURE__ */ new Set();
|
|
14295
|
+
for (const call of calls) {
|
|
14296
|
+
if (call.is_constructor && call.arguments.length === 0) {
|
|
14297
|
+
zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14300
|
+
return sinks.filter((sink) => {
|
|
14301
|
+
if (sink.type !== "sql_injection") return true;
|
|
14302
|
+
if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
|
|
14303
|
+
return false;
|
|
14304
|
+
}
|
|
14305
|
+
return true;
|
|
14306
|
+
});
|
|
14307
|
+
}
|
|
14293
14308
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
14294
14309
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
14295
14310
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
14296
14311
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
14297
14312
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14298
14313
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14314
|
+
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14299
14315
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
14300
|
-
return { sources, sinks, sanitizers };
|
|
14316
|
+
return { sources, sinks: gatedSinks, sanitizers };
|
|
14301
14317
|
}
|
|
14302
14318
|
function sinkPatternAppliesTo(pattern, language) {
|
|
14303
14319
|
if (language === void 0) return true;
|
|
@@ -14568,7 +14584,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14568
14584
|
}
|
|
14569
14585
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
14570
14586
|
for (const source of sources) {
|
|
14571
|
-
const key = `${source.line}:${source.type}`;
|
|
14587
|
+
const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
|
|
14572
14588
|
const existing = sourceMap.get(key);
|
|
14573
14589
|
if (!existing || source.confidence > existing.confidence) {
|
|
14574
14590
|
sourceMap.set(key, source);
|
|
@@ -15068,7 +15084,7 @@ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)
|
|
|
15068
15084
|
const guards = guardMap.get(cand);
|
|
15069
15085
|
if (!guards) continue;
|
|
15070
15086
|
for (const g of guards) {
|
|
15071
|
-
if (g.i
|
|
15087
|
+
if (g.i > sinkIdx || checked.has(g)) continue;
|
|
15072
15088
|
checked.add(g);
|
|
15073
15089
|
const block = csThenBlock(sourceLines, g.i, g.rest);
|
|
15074
15090
|
if (g.op === "==") {
|
|
@@ -15186,6 +15202,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
15186
15202
|
// hutool
|
|
15187
15203
|
"RuntimeUtil"
|
|
15188
15204
|
]);
|
|
15205
|
+
function isRegexReceiver(receiver, sourceLines) {
|
|
15206
|
+
const r = (receiver ?? "").trim();
|
|
15207
|
+
if (r.length === 0) return false;
|
|
15208
|
+
if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
|
|
15209
|
+
if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
|
|
15210
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
|
|
15211
|
+
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
15212
|
+
return sourceLines.some((l) => decl.test(l));
|
|
15213
|
+
}
|
|
15189
15214
|
function isFunctionCallbackArgument(arg) {
|
|
15190
15215
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
15191
15216
|
const expr = (arg.expression ?? "").trim();
|
|
@@ -15368,6 +15393,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15368
15393
|
continue;
|
|
15369
15394
|
}
|
|
15370
15395
|
}
|
|
15396
|
+
if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15397
|
+
continue;
|
|
15398
|
+
}
|
|
15399
|
+
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
15400
|
+
continue;
|
|
15401
|
+
}
|
|
15371
15402
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
15372
15403
|
continue;
|
|
15373
15404
|
}
|
|
@@ -16683,6 +16714,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
16683
16714
|
for (const source of sources) {
|
|
16684
16715
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
16685
16716
|
for (const def of defsOnLine) {
|
|
16717
|
+
if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
16718
|
+
continue;
|
|
16719
|
+
}
|
|
16686
16720
|
tainted.push({
|
|
16687
16721
|
variable: def.variable,
|
|
16688
16722
|
defId: def.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.12",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|