circle-ir 3.141.0 → 3.144.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/configs/sink-semantics.json +53 -0
- package/dist/analysis/config-loader.d.ts +24 -2
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +92 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts +32 -0
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +52 -2
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +37 -4
- package/dist/analysis/passes/scan-secrets-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 +10 -1
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/sink-semantics-pass.d.ts +52 -0
- package/dist/analysis/passes/sink-semantics-pass.d.ts.map +1 -0
- package/dist/analysis/passes/sink-semantics-pass.js +100 -0
- package/dist/analysis/passes/sink-semantics-pass.js.map +1 -0
- package/dist/analysis/passes/source-semantics-pass.d.ts +66 -0
- package/dist/analysis/passes/source-semantics-pass.d.ts.map +1 -0
- package/dist/analysis/passes/source-semantics-pass.js +165 -0
- package/dist/analysis/passes/source-semantics-pass.js.map +1 -0
- package/dist/analysis/passes/taint-propagation-pass.js +60 -9
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +10 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +14 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +238 -10
- package/dist/core/circle-ir-core.cjs +85 -4
- package/dist/core/circle-ir-core.js +85 -4
- package/dist/core/extractors/calls.js +14 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/types/config.d.ts +47 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +43 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -6337,6 +6337,12 @@ function resolveReceiverType(receiver, context) {
|
|
|
6337
6337
|
return { simpleName: null, fqn: null };
|
|
6338
6338
|
}
|
|
6339
6339
|
if (receiver === "super") return { simpleName: null, fqn: null };
|
|
6340
|
+
const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
|
|
6341
|
+
if (ctorMatch) {
|
|
6342
|
+
const ctorClass = ctorMatch[1];
|
|
6343
|
+
const simple = ctorClass.includes(".") ? ctorClass.substring(ctorClass.lastIndexOf(".") + 1) : ctorClass;
|
|
6344
|
+
return resolveFqn(simple, context);
|
|
6345
|
+
}
|
|
6340
6346
|
const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
|
|
6341
6347
|
if (declaredType) {
|
|
6342
6348
|
return resolveFqn(stripGenerics(declaredType), context);
|
|
@@ -11451,6 +11457,14 @@ var DEFAULT_SINKS = [
|
|
|
11451
11457
|
// reflection remains a downstream concern of body-writing sinks.
|
|
11452
11458
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
11453
11459
|
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
11460
|
+
// Cookie constructor + addCookie — HTTP response splitting via cookie
|
|
11461
|
+
// name/value (CWE-113). Reflects the #189 Sprint 92 V02SetCookie fixture
|
|
11462
|
+
// where `res.addCookie(new Cookie(name, req.getParameter("v")))` allows
|
|
11463
|
+
// CRLF injection into the Set-Cookie header. Both the ctor arg[1] (value)
|
|
11464
|
+
// and the addCookie arg[0] (Cookie handle) are flagged so intermediate-var
|
|
11465
|
+
// and inline-ctor shapes both surface.
|
|
11466
|
+
{ method: "Cookie", class: "constructor", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0, 1] },
|
|
11467
|
+
{ method: "addCookie", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0] },
|
|
11454
11468
|
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
11455
11469
|
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
11456
11470
|
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
@@ -12890,11 +12904,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
12890
12904
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
12891
12905
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
12892
12906
|
];
|
|
12907
|
+
var DEFAULT_SINK_SEMANTICS = [
|
|
12908
|
+
{
|
|
12909
|
+
signature: "Jedis#executeCommand",
|
|
12910
|
+
real_class: "db_protocol",
|
|
12911
|
+
overrides: ["command_injection", "code_injection"],
|
|
12912
|
+
note: "Redis wire-protocol serialization, not OS exec"
|
|
12913
|
+
},
|
|
12914
|
+
{
|
|
12915
|
+
signature: "Connection#executeCommand",
|
|
12916
|
+
real_class: "db_protocol",
|
|
12917
|
+
overrides: ["command_injection", "code_injection"],
|
|
12918
|
+
note: "Jedis abstract Connection base"
|
|
12919
|
+
},
|
|
12920
|
+
{
|
|
12921
|
+
signature: "JedisCluster#executeCommand",
|
|
12922
|
+
real_class: "db_protocol",
|
|
12923
|
+
overrides: ["command_injection", "code_injection"],
|
|
12924
|
+
note: "Jedis cluster client"
|
|
12925
|
+
},
|
|
12926
|
+
{
|
|
12927
|
+
signature: "Func1#exec",
|
|
12928
|
+
real_class: "functional_dispatch",
|
|
12929
|
+
overrides: ["command_injection", "code_injection"],
|
|
12930
|
+
note: "RxJava functional dispatch, not OS exec"
|
|
12931
|
+
},
|
|
12932
|
+
{
|
|
12933
|
+
signature: "Action0#call",
|
|
12934
|
+
real_class: "functional_dispatch",
|
|
12935
|
+
overrides: ["command_injection"],
|
|
12936
|
+
note: "RxJava Action0 dispatch"
|
|
12937
|
+
},
|
|
12938
|
+
{
|
|
12939
|
+
signature: "Action1#call",
|
|
12940
|
+
real_class: "functional_dispatch",
|
|
12941
|
+
overrides: ["command_injection"],
|
|
12942
|
+
note: "RxJava Action1 dispatch"
|
|
12943
|
+
},
|
|
12944
|
+
{
|
|
12945
|
+
signature: "Unsafe#defineAnonymousClass",
|
|
12946
|
+
real_class: "jdk_internal",
|
|
12947
|
+
overrides: ["code_injection"],
|
|
12948
|
+
note: "sun.misc.Unsafe JDK-internal reflective bridge"
|
|
12949
|
+
},
|
|
12950
|
+
{
|
|
12951
|
+
signature: "MethodHandle#invokeExact",
|
|
12952
|
+
real_class: "jdk_internal",
|
|
12953
|
+
overrides: ["code_injection"],
|
|
12954
|
+
note: "java.lang.invoke.MethodHandle \u2014 JDK-internal"
|
|
12955
|
+
}
|
|
12956
|
+
];
|
|
12893
12957
|
function getDefaultConfig() {
|
|
12894
12958
|
return {
|
|
12895
12959
|
sources: DEFAULT_SOURCES,
|
|
12896
12960
|
sinks: DEFAULT_SINKS,
|
|
12897
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
12961
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
12962
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
12898
12963
|
};
|
|
12899
12964
|
}
|
|
12900
12965
|
var DEFAULT_HEADER_RULES = [
|
|
@@ -13738,6 +13803,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13738
13803
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
13739
13804
|
const existing = sinkMap.get(key);
|
|
13740
13805
|
if (!existing || confidence > existing.confidence) {
|
|
13806
|
+
const receiverType = call.receiver_type;
|
|
13807
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
13741
13808
|
sinkMap.set(key, {
|
|
13742
13809
|
type: pattern.type,
|
|
13743
13810
|
cwe: pattern.cwe,
|
|
@@ -13745,7 +13812,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13745
13812
|
line: call.location.line,
|
|
13746
13813
|
confidence,
|
|
13747
13814
|
method: call.method_name,
|
|
13748
|
-
argPositions: pattern.arg_positions
|
|
13815
|
+
argPositions: pattern.arg_positions,
|
|
13816
|
+
class: simpleClass
|
|
13749
13817
|
});
|
|
13750
13818
|
}
|
|
13751
13819
|
}
|
|
@@ -14700,12 +14768,20 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
14700
14768
|
// flow detector silently dropped `http_* → trust_boundary` co-located
|
|
14701
14769
|
// flows like `req.getSession().setAttribute("u", req.getParameter("u"))`
|
|
14702
14770
|
// (0% recall on OWASP Java trustbound category).
|
|
14703
|
-
|
|
14771
|
+
// deserialization added Sprint 93 (#189): SnakeYAML/Jackson/etc. sinks
|
|
14772
|
+
// like `new Yaml().load(req.getParameter("y"))` are real RCE gadget chains
|
|
14773
|
+
// (CWE-502). The reach map previously restricted deserialization to
|
|
14774
|
+
// http_body so http_param/http_query request-derived values feeding
|
|
14775
|
+
// Yaml.load, ObjectInputStream ctor, XMLDecoder ctor, etc. silently
|
|
14776
|
+
// dropped their inline-colocation flow. Typed-overload FPs are gated by
|
|
14777
|
+
// the sink pattern's `safe_if_class_literal_at` flag (Jackson readValue,
|
|
14778
|
+
// Yaml.loadAs, Gson.fromJson) so the wider reach does not regress.
|
|
14779
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization"],
|
|
14704
14780
|
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
14705
14781
|
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
14706
14782
|
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
14707
14783
|
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary"],
|
|
14708
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
14784
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization"],
|
|
14709
14785
|
// ssrf added Sprint 57 #200: bash CGI/webhook handlers and scripts that
|
|
14710
14786
|
// take a URL on stdin or as a positional CLI arg (`curl "$1"`,
|
|
14711
14787
|
// `wget "$(read line)"`) and curl/wget it server-side are textbook SSRF
|
|
@@ -14727,6 +14803,15 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
14727
14803
|
const validSinks = sourceToSinkMapping[sourceType];
|
|
14728
14804
|
return validSinks ? validSinks.includes(sinkType) : false;
|
|
14729
14805
|
}
|
|
14806
|
+
function sourceSemanticsAllowed(source, sinkType) {
|
|
14807
|
+
if (source.constant === true) {
|
|
14808
|
+
return false;
|
|
14809
|
+
}
|
|
14810
|
+
if (source.spi === true) {
|
|
14811
|
+
return sinkType === "code_injection";
|
|
14812
|
+
}
|
|
14813
|
+
return true;
|
|
14814
|
+
}
|
|
14730
14815
|
|
|
14731
14816
|
// src/analysis/findings-instrumentation.ts
|
|
14732
14817
|
var instrumentEnabled = false;
|
|
@@ -28967,6 +29052,80 @@ function findJsTemplateInjectionSstiFindings(code, file) {
|
|
|
28967
29052
|
return findings;
|
|
28968
29053
|
}
|
|
28969
29054
|
|
|
29055
|
+
// src/analysis/passes/source-semantics-pass.ts
|
|
29056
|
+
var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
|
|
29057
|
+
var CONST_STRING_ASSIGN_RE = /^\s*(?:final\s+|static\s+final\s+)?[A-Za-z_][\w.<>\[\]]*\s+[A-Za-z_]\w*\s*=\s*"[^"]*"\s*;?\s*$/;
|
|
29058
|
+
var STATIC_FINAL_RE = /^\s*(?:public\s+|private\s+|protected\s+)?static\s+final\s+/;
|
|
29059
|
+
var ENUM_CONST_REF_RE = /=\s*[A-Z][A-Za-z0-9_]*\.[A-Z][A-Z0-9_]*\s*;?\s*$/;
|
|
29060
|
+
function isConstantSource(code) {
|
|
29061
|
+
if (!code) return false;
|
|
29062
|
+
if (CONST_STRING_ASSIGN_RE.test(code)) return true;
|
|
29063
|
+
if (STATIC_FINAL_RE.test(code)) {
|
|
29064
|
+
const rhs = code.split("=").slice(1).join("=").trim();
|
|
29065
|
+
if (rhs.length === 0) return false;
|
|
29066
|
+
if (/^"[^"]*"\s*;?\s*$/.test(rhs)) return true;
|
|
29067
|
+
if (/^-?\d+(?:\.\d+)?[fFdDlL]?\s*;?\s*$/.test(rhs)) return true;
|
|
29068
|
+
if (/^(?:true|false)\s*;?\s*$/.test(rhs)) return true;
|
|
29069
|
+
if (/^[A-Za-z_][\w.]*\s*;?\s*$/.test(rhs)) return true;
|
|
29070
|
+
return false;
|
|
29071
|
+
}
|
|
29072
|
+
if (ENUM_CONST_REF_RE.test(code)) return true;
|
|
29073
|
+
return false;
|
|
29074
|
+
}
|
|
29075
|
+
var SERVICE_LOADER_RE = /\bServiceLoader\.(?:load|loadInstalled|stream)\s*\(/;
|
|
29076
|
+
var CLASS_FOR_NAME_RE = /\bClass\.forName\s*\(/;
|
|
29077
|
+
var META_INF_SERVICES_RE = /getResources?\s*\(\s*"META-INF\/services\//;
|
|
29078
|
+
var SPI_WINDOW = 30;
|
|
29079
|
+
function isSpiSource(source, lines) {
|
|
29080
|
+
const code = source.code;
|
|
29081
|
+
if (!code) return false;
|
|
29082
|
+
if (SERVICE_LOADER_RE.test(code)) return true;
|
|
29083
|
+
if (CLASS_FOR_NAME_RE.test(code)) {
|
|
29084
|
+
const start2 = Math.max(0, source.line - 1 - SPI_WINDOW);
|
|
29085
|
+
const end = Math.min(lines.length, source.line - 1 + SPI_WINDOW + 1);
|
|
29086
|
+
for (let i2 = start2; i2 < end; i2++) {
|
|
29087
|
+
if (META_INF_SERVICES_RE.test(lines[i2])) return true;
|
|
29088
|
+
}
|
|
29089
|
+
}
|
|
29090
|
+
return false;
|
|
29091
|
+
}
|
|
29092
|
+
function isDemoPathFile(file) {
|
|
29093
|
+
if (!file) return false;
|
|
29094
|
+
return DEMO_PATH_RE.test(file);
|
|
29095
|
+
}
|
|
29096
|
+
var SourceSemanticsPass = class {
|
|
29097
|
+
name = "source-semantics";
|
|
29098
|
+
category = "security";
|
|
29099
|
+
run(ctx) {
|
|
29100
|
+
const { graph, code } = ctx;
|
|
29101
|
+
const sources = graph.ir.taint.sources;
|
|
29102
|
+
if (sources.length === 0) {
|
|
29103
|
+
return { constantCount: 0, spiCount: 0, demoPathCount: 0 };
|
|
29104
|
+
}
|
|
29105
|
+
const file = graph.ir.meta.file;
|
|
29106
|
+
const demoPath = isDemoPathFile(file);
|
|
29107
|
+
const lines = code.split("\n");
|
|
29108
|
+
let constantCount = 0;
|
|
29109
|
+
let spiCount = 0;
|
|
29110
|
+
let demoPathCount = 0;
|
|
29111
|
+
for (const source of sources) {
|
|
29112
|
+
if (isConstantSource(source.code)) {
|
|
29113
|
+
source.constant = true;
|
|
29114
|
+
constantCount++;
|
|
29115
|
+
}
|
|
29116
|
+
if (isSpiSource(source, lines)) {
|
|
29117
|
+
source.spi = true;
|
|
29118
|
+
spiCount++;
|
|
29119
|
+
}
|
|
29120
|
+
if (demoPath) {
|
|
29121
|
+
source.demoPath = true;
|
|
29122
|
+
demoPathCount++;
|
|
29123
|
+
}
|
|
29124
|
+
}
|
|
29125
|
+
return { constantCount, spiCount, demoPathCount };
|
|
29126
|
+
}
|
|
29127
|
+
};
|
|
29128
|
+
|
|
28970
29129
|
// src/analysis/passes/sink-filter-pass.ts
|
|
28971
29130
|
var JS_XSS_SANITIZERS = [
|
|
28972
29131
|
/\bDOMPurify\.sanitize\s*\(/,
|
|
@@ -29514,7 +29673,7 @@ var SinkFilterPass = class {
|
|
|
29514
29673
|
const sourceLines = ctx.code.split("\n");
|
|
29515
29674
|
filtered = filtered.filter((sink) => {
|
|
29516
29675
|
if (sink.type !== "command_injection") return true;
|
|
29517
|
-
if (sink.method !== "ProcessBuilder") return true;
|
|
29676
|
+
if (sink.method !== "ProcessBuilder" && sink.method !== "start") return true;
|
|
29518
29677
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
29519
29678
|
if (!/\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText)) return true;
|
|
29520
29679
|
if (PROCESS_BUILDER_ARGV_FORM_RE.test(sinkLineText)) return false;
|
|
@@ -30258,6 +30417,50 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
30258
30417
|
});
|
|
30259
30418
|
}
|
|
30260
30419
|
|
|
30420
|
+
// src/analysis/passes/sink-semantics-pass.ts
|
|
30421
|
+
function buildRegistry(entries) {
|
|
30422
|
+
const registry = /* @__PURE__ */ new Map();
|
|
30423
|
+
for (const entry of entries) {
|
|
30424
|
+
const existing = registry.get(entry.signature);
|
|
30425
|
+
if (existing) {
|
|
30426
|
+
for (const t of entry.overrides) existing.add(t);
|
|
30427
|
+
} else {
|
|
30428
|
+
registry.set(entry.signature, new Set(entry.overrides));
|
|
30429
|
+
}
|
|
30430
|
+
}
|
|
30431
|
+
return registry;
|
|
30432
|
+
}
|
|
30433
|
+
var SinkSemanticsPass = class {
|
|
30434
|
+
name = "sink-semantics";
|
|
30435
|
+
category = "security";
|
|
30436
|
+
run(ctx) {
|
|
30437
|
+
const { graph, config } = ctx;
|
|
30438
|
+
const entries = config.sinkSemantics ?? [];
|
|
30439
|
+
if (entries.length === 0) {
|
|
30440
|
+
return { droppedCount: 0, registrySize: 0 };
|
|
30441
|
+
}
|
|
30442
|
+
const registry = buildRegistry(entries);
|
|
30443
|
+
const sinks = graph.ir.taint.sinks;
|
|
30444
|
+
let droppedCount = 0;
|
|
30445
|
+
const kept = sinks.filter((sink) => {
|
|
30446
|
+
if (!sink.class || !sink.method) return true;
|
|
30447
|
+
const signature = `${sink.class}#${sink.method}`;
|
|
30448
|
+
const overrides = registry.get(signature);
|
|
30449
|
+
if (!overrides) return true;
|
|
30450
|
+
if (overrides.has(sink.type)) {
|
|
30451
|
+
droppedCount++;
|
|
30452
|
+
return false;
|
|
30453
|
+
}
|
|
30454
|
+
return true;
|
|
30455
|
+
});
|
|
30456
|
+
if (droppedCount > 0) {
|
|
30457
|
+
sinks.length = 0;
|
|
30458
|
+
sinks.push(...kept);
|
|
30459
|
+
}
|
|
30460
|
+
return { droppedCount, registrySize: registry.size };
|
|
30461
|
+
}
|
|
30462
|
+
};
|
|
30463
|
+
|
|
30261
30464
|
// src/analysis/passes/taint-propagation-pass.ts
|
|
30262
30465
|
var TaintPropagationPass = class {
|
|
30263
30466
|
name = "taint-propagation";
|
|
@@ -30916,6 +31119,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30916
31119
|
}
|
|
30917
31120
|
const re = reCache.get(source.variable);
|
|
30918
31121
|
if (!re || !re.test(expr)) continue;
|
|
31122
|
+
if (!sourceSemanticsAllowed(source, sink.type)) continue;
|
|
30919
31123
|
if (flows.some(
|
|
30920
31124
|
(f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type
|
|
30921
31125
|
)) continue;
|
|
@@ -30944,7 +31148,6 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30944
31148
|
}
|
|
30945
31149
|
const sourcesByLine = /* @__PURE__ */ new Map();
|
|
30946
31150
|
for (const s of sources) {
|
|
30947
|
-
if (s.variable && s.variable.length > 0) continue;
|
|
30948
31151
|
const arr = sourcesByLine.get(s.line) ?? [];
|
|
30949
31152
|
arr.push(s);
|
|
30950
31153
|
sourcesByLine.set(s.line, arr);
|
|
@@ -30955,6 +31158,19 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30955
31158
|
if (!colocSources || colocSources.length === 0) continue;
|
|
30956
31159
|
for (const source of colocSources) {
|
|
30957
31160
|
if (!canSourceReachSink(source.type, sink.type)) continue;
|
|
31161
|
+
if (!sourceSemanticsAllowed(source, sink.type)) continue;
|
|
31162
|
+
const sourceVar = source.variable;
|
|
31163
|
+
if (sourceVar && sourceVar.length > 0) {
|
|
31164
|
+
const sinkCode = sink.code;
|
|
31165
|
+
if (!sinkCode) {
|
|
31166
|
+
continue;
|
|
31167
|
+
}
|
|
31168
|
+
const assignMatch = sinkCode.match(/^\s*(?:[A-Za-z_][\w.<>[\]\s,?]*\s+)?[A-Za-z_]\w*\s*=(?!=)\s*/);
|
|
31169
|
+
const rhs = assignMatch ? sinkCode.slice(assignMatch[0].length) : sinkCode;
|
|
31170
|
+
if (new RegExp(`\\b${sourceVar}\\b`).test(rhs)) {
|
|
31171
|
+
continue;
|
|
31172
|
+
}
|
|
31173
|
+
}
|
|
30958
31174
|
if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
|
|
30959
31175
|
continue;
|
|
30960
31176
|
}
|
|
@@ -35059,6 +35275,13 @@ function isProtocolMandatedCryptoFile(file, code) {
|
|
|
35059
35275
|
}
|
|
35060
35276
|
|
|
35061
35277
|
// src/analysis/passes/scan-secrets-pass.ts
|
|
35278
|
+
function applyDemoDowngrade(demoPath, severity, level) {
|
|
35279
|
+
if (!demoPath) return { severity, level };
|
|
35280
|
+
if (severity === "high") {
|
|
35281
|
+
return { severity: "low", level: "note" };
|
|
35282
|
+
}
|
|
35283
|
+
return { severity, level };
|
|
35284
|
+
}
|
|
35062
35285
|
var TEST_PATH_RE3 = /(?:^|[\\/])(?:test|tests|spec|specs|__tests?__|__mocks?__|fixtures?|testdata)(?:[\\/]|$)/i;
|
|
35063
35286
|
var TEST_FILENAME_RE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|_test\.go|_test\.py|Test\.java|Tests\.java)$/i;
|
|
35064
35287
|
function isTestFile(file) {
|
|
@@ -35374,6 +35597,7 @@ var ScanSecretsPass = class {
|
|
|
35374
35597
|
if (isTestFile(file) || isGeneratedFile(file)) {
|
|
35375
35598
|
return { providerFindings: 0, entropyFindings: 0 };
|
|
35376
35599
|
}
|
|
35600
|
+
const demoPath = DEMO_PATH_RE.test(file);
|
|
35377
35601
|
const lines = ctx.code.split("\n");
|
|
35378
35602
|
const prior = ctx.getFindings?.() ?? [];
|
|
35379
35603
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -35400,14 +35624,15 @@ var ScanSecretsPass = class {
|
|
|
35400
35624
|
const key = `${lineNum}:hardcoded-credential`;
|
|
35401
35625
|
if (seen.has(key)) continue;
|
|
35402
35626
|
seen.add(key);
|
|
35627
|
+
const dg = applyDemoDowngrade(demoPath, pattern.severity, pattern.level);
|
|
35403
35628
|
ctx.addFinding({
|
|
35404
35629
|
id: `hardcoded-credential-${file}-${lineNum}`,
|
|
35405
35630
|
pass: this.name,
|
|
35406
35631
|
category: this.category,
|
|
35407
35632
|
rule_id: "hardcoded-credential",
|
|
35408
35633
|
cwe: "CWE-798",
|
|
35409
|
-
severity:
|
|
35410
|
-
level:
|
|
35634
|
+
severity: dg.severity,
|
|
35635
|
+
level: dg.level,
|
|
35411
35636
|
message: `Hardcoded credential: ${pattern.name} detected`,
|
|
35412
35637
|
file,
|
|
35413
35638
|
line: lineNum,
|
|
@@ -35427,14 +35652,15 @@ var ScanSecretsPass = class {
|
|
|
35427
35652
|
const key = `${lineNum}:hardcoded-credential`;
|
|
35428
35653
|
if (seen.has(key)) continue;
|
|
35429
35654
|
seen.add(key);
|
|
35655
|
+
const dg = applyDemoDowngrade(demoPath, "high", "error");
|
|
35430
35656
|
ctx.addFinding({
|
|
35431
35657
|
id: `hardcoded-credential-${file}-${lineNum}`,
|
|
35432
35658
|
pass: this.name,
|
|
35433
35659
|
category: this.category,
|
|
35434
35660
|
rule_id: "hardcoded-credential",
|
|
35435
35661
|
cwe: "CWE-798",
|
|
35436
|
-
severity:
|
|
35437
|
-
level:
|
|
35662
|
+
severity: dg.severity,
|
|
35663
|
+
level: dg.level,
|
|
35438
35664
|
message: `Hardcoded credential: \`${hit.name}\` assigned a literal value`,
|
|
35439
35665
|
file,
|
|
35440
35666
|
line: lineNum,
|
|
@@ -39748,7 +39974,9 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
39748
39974
|
pipeline.add(new TaintMatcherPass());
|
|
39749
39975
|
pipeline.add(new ConstantPropagationPass(tree));
|
|
39750
39976
|
pipeline.add(new LanguageSourcesPass());
|
|
39977
|
+
if (!disabledPasses.has("source-semantics")) pipeline.add(new SourceSemanticsPass());
|
|
39751
39978
|
pipeline.add(new SinkFilterPass());
|
|
39979
|
+
if (!disabledPasses.has("sink-semantics")) pipeline.add(new SinkSemanticsPass());
|
|
39752
39980
|
pipeline.add(new TaintPropagationPass());
|
|
39753
39981
|
pipeline.add(new InterproceduralPass({
|
|
39754
39982
|
enableEntryPointGate: options.enableEntryPointGate ?? true
|
|
@@ -6383,6 +6383,12 @@ function resolveReceiverType(receiver, context) {
|
|
|
6383
6383
|
return { simpleName: null, fqn: null };
|
|
6384
6384
|
}
|
|
6385
6385
|
if (receiver === "super") return { simpleName: null, fqn: null };
|
|
6386
|
+
const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
|
|
6387
|
+
if (ctorMatch) {
|
|
6388
|
+
const ctorClass = ctorMatch[1];
|
|
6389
|
+
const simple = ctorClass.includes(".") ? ctorClass.substring(ctorClass.lastIndexOf(".") + 1) : ctorClass;
|
|
6390
|
+
return resolveFqn(simple, context);
|
|
6391
|
+
}
|
|
6386
6392
|
const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
|
|
6387
6393
|
if (declaredType) {
|
|
6388
6394
|
return resolveFqn(stripGenerics(declaredType), context);
|
|
@@ -10109,12 +10115,25 @@ function loadSinkConfigs(configs) {
|
|
|
10109
10115
|
}
|
|
10110
10116
|
return { sinks, sanitizers };
|
|
10111
10117
|
}
|
|
10112
|
-
function
|
|
10118
|
+
function loadSinkSemanticsConfigs(configs) {
|
|
10119
|
+
const entries = [];
|
|
10120
|
+
for (const config of configs) {
|
|
10121
|
+
if (config.sinks) {
|
|
10122
|
+
entries.push(...config.sinks);
|
|
10123
|
+
}
|
|
10124
|
+
}
|
|
10125
|
+
return entries;
|
|
10126
|
+
}
|
|
10127
|
+
function createTaintConfig(sourceContents, sinkContents, sinkSemanticsContents = []) {
|
|
10113
10128
|
const sourceConfigs = sourceContents.map((c) => parseConfig(c));
|
|
10114
10129
|
const sinkConfigs = sinkContents.map((c) => parseConfig(c));
|
|
10130
|
+
const sinkSemanticsConfigs = sinkSemanticsContents.map(
|
|
10131
|
+
(c) => parseConfig(c)
|
|
10132
|
+
);
|
|
10115
10133
|
const sources = loadSourceConfigs(sourceConfigs);
|
|
10116
10134
|
const { sinks, sanitizers } = loadSinkConfigs(sinkConfigs);
|
|
10117
|
-
|
|
10135
|
+
const sinkSemantics = loadSinkSemanticsConfigs(sinkSemanticsConfigs);
|
|
10136
|
+
return { sources, sinks, sanitizers, sinkSemantics };
|
|
10118
10137
|
}
|
|
10119
10138
|
var DEFAULT_SOURCES = [
|
|
10120
10139
|
// HTTP Sources (Servlet API)
|
|
@@ -10833,6 +10852,14 @@ var DEFAULT_SINKS = [
|
|
|
10833
10852
|
// reflection remains a downstream concern of body-writing sinks.
|
|
10834
10853
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10835
10854
|
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10855
|
+
// Cookie constructor + addCookie — HTTP response splitting via cookie
|
|
10856
|
+
// name/value (CWE-113). Reflects the #189 Sprint 92 V02SetCookie fixture
|
|
10857
|
+
// where `res.addCookie(new Cookie(name, req.getParameter("v")))` allows
|
|
10858
|
+
// CRLF injection into the Set-Cookie header. Both the ctor arg[1] (value)
|
|
10859
|
+
// and the addCookie arg[0] (Cookie handle) are flagged so intermediate-var
|
|
10860
|
+
// and inline-ctor shapes both surface.
|
|
10861
|
+
{ method: "Cookie", class: "constructor", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0, 1] },
|
|
10862
|
+
{ method: "addCookie", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0] },
|
|
10836
10863
|
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
10837
10864
|
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
10838
10865
|
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
@@ -12272,11 +12299,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
12272
12299
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
12273
12300
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
12274
12301
|
];
|
|
12302
|
+
var DEFAULT_SINK_SEMANTICS = [
|
|
12303
|
+
{
|
|
12304
|
+
signature: "Jedis#executeCommand",
|
|
12305
|
+
real_class: "db_protocol",
|
|
12306
|
+
overrides: ["command_injection", "code_injection"],
|
|
12307
|
+
note: "Redis wire-protocol serialization, not OS exec"
|
|
12308
|
+
},
|
|
12309
|
+
{
|
|
12310
|
+
signature: "Connection#executeCommand",
|
|
12311
|
+
real_class: "db_protocol",
|
|
12312
|
+
overrides: ["command_injection", "code_injection"],
|
|
12313
|
+
note: "Jedis abstract Connection base"
|
|
12314
|
+
},
|
|
12315
|
+
{
|
|
12316
|
+
signature: "JedisCluster#executeCommand",
|
|
12317
|
+
real_class: "db_protocol",
|
|
12318
|
+
overrides: ["command_injection", "code_injection"],
|
|
12319
|
+
note: "Jedis cluster client"
|
|
12320
|
+
},
|
|
12321
|
+
{
|
|
12322
|
+
signature: "Func1#exec",
|
|
12323
|
+
real_class: "functional_dispatch",
|
|
12324
|
+
overrides: ["command_injection", "code_injection"],
|
|
12325
|
+
note: "RxJava functional dispatch, not OS exec"
|
|
12326
|
+
},
|
|
12327
|
+
{
|
|
12328
|
+
signature: "Action0#call",
|
|
12329
|
+
real_class: "functional_dispatch",
|
|
12330
|
+
overrides: ["command_injection"],
|
|
12331
|
+
note: "RxJava Action0 dispatch"
|
|
12332
|
+
},
|
|
12333
|
+
{
|
|
12334
|
+
signature: "Action1#call",
|
|
12335
|
+
real_class: "functional_dispatch",
|
|
12336
|
+
overrides: ["command_injection"],
|
|
12337
|
+
note: "RxJava Action1 dispatch"
|
|
12338
|
+
},
|
|
12339
|
+
{
|
|
12340
|
+
signature: "Unsafe#defineAnonymousClass",
|
|
12341
|
+
real_class: "jdk_internal",
|
|
12342
|
+
overrides: ["code_injection"],
|
|
12343
|
+
note: "sun.misc.Unsafe JDK-internal reflective bridge"
|
|
12344
|
+
},
|
|
12345
|
+
{
|
|
12346
|
+
signature: "MethodHandle#invokeExact",
|
|
12347
|
+
real_class: "jdk_internal",
|
|
12348
|
+
overrides: ["code_injection"],
|
|
12349
|
+
note: "java.lang.invoke.MethodHandle \u2014 JDK-internal"
|
|
12350
|
+
}
|
|
12351
|
+
];
|
|
12275
12352
|
function getDefaultConfig() {
|
|
12276
12353
|
return {
|
|
12277
12354
|
sources: DEFAULT_SOURCES,
|
|
12278
12355
|
sinks: DEFAULT_SINKS,
|
|
12279
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
12356
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
12357
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
12280
12358
|
};
|
|
12281
12359
|
}
|
|
12282
12360
|
|
|
@@ -13033,6 +13111,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13033
13111
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
13034
13112
|
const existing = sinkMap.get(key);
|
|
13035
13113
|
if (!existing || confidence > existing.confidence) {
|
|
13114
|
+
const receiverType = call.receiver_type;
|
|
13115
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
13036
13116
|
sinkMap.set(key, {
|
|
13037
13117
|
type: pattern.type,
|
|
13038
13118
|
cwe: pattern.cwe,
|
|
@@ -13040,7 +13120,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13040
13120
|
line: call.location.line,
|
|
13041
13121
|
confidence,
|
|
13042
13122
|
method: call.method_name,
|
|
13043
|
-
argPositions: pattern.arg_positions
|
|
13123
|
+
argPositions: pattern.arg_positions,
|
|
13124
|
+
class: simpleClass
|
|
13044
13125
|
});
|
|
13045
13126
|
}
|
|
13046
13127
|
}
|
|
@@ -6317,6 +6317,12 @@ function resolveReceiverType(receiver, context) {
|
|
|
6317
6317
|
return { simpleName: null, fqn: null };
|
|
6318
6318
|
}
|
|
6319
6319
|
if (receiver === "super") return { simpleName: null, fqn: null };
|
|
6320
|
+
const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
|
|
6321
|
+
if (ctorMatch) {
|
|
6322
|
+
const ctorClass = ctorMatch[1];
|
|
6323
|
+
const simple = ctorClass.includes(".") ? ctorClass.substring(ctorClass.lastIndexOf(".") + 1) : ctorClass;
|
|
6324
|
+
return resolveFqn(simple, context);
|
|
6325
|
+
}
|
|
6320
6326
|
const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
|
|
6321
6327
|
if (declaredType) {
|
|
6322
6328
|
return resolveFqn(stripGenerics(declaredType), context);
|
|
@@ -10043,12 +10049,25 @@ function loadSinkConfigs(configs) {
|
|
|
10043
10049
|
}
|
|
10044
10050
|
return { sinks, sanitizers };
|
|
10045
10051
|
}
|
|
10046
|
-
function
|
|
10052
|
+
function loadSinkSemanticsConfigs(configs) {
|
|
10053
|
+
const entries = [];
|
|
10054
|
+
for (const config of configs) {
|
|
10055
|
+
if (config.sinks) {
|
|
10056
|
+
entries.push(...config.sinks);
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
return entries;
|
|
10060
|
+
}
|
|
10061
|
+
function createTaintConfig(sourceContents, sinkContents, sinkSemanticsContents = []) {
|
|
10047
10062
|
const sourceConfigs = sourceContents.map((c) => parseConfig(c));
|
|
10048
10063
|
const sinkConfigs = sinkContents.map((c) => parseConfig(c));
|
|
10064
|
+
const sinkSemanticsConfigs = sinkSemanticsContents.map(
|
|
10065
|
+
(c) => parseConfig(c)
|
|
10066
|
+
);
|
|
10049
10067
|
const sources = loadSourceConfigs(sourceConfigs);
|
|
10050
10068
|
const { sinks, sanitizers } = loadSinkConfigs(sinkConfigs);
|
|
10051
|
-
|
|
10069
|
+
const sinkSemantics = loadSinkSemanticsConfigs(sinkSemanticsConfigs);
|
|
10070
|
+
return { sources, sinks, sanitizers, sinkSemantics };
|
|
10052
10071
|
}
|
|
10053
10072
|
var DEFAULT_SOURCES = [
|
|
10054
10073
|
// HTTP Sources (Servlet API)
|
|
@@ -10767,6 +10786,14 @@ var DEFAULT_SINKS = [
|
|
|
10767
10786
|
// reflection remains a downstream concern of body-writing sinks.
|
|
10768
10787
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10769
10788
|
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10789
|
+
// Cookie constructor + addCookie — HTTP response splitting via cookie
|
|
10790
|
+
// name/value (CWE-113). Reflects the #189 Sprint 92 V02SetCookie fixture
|
|
10791
|
+
// where `res.addCookie(new Cookie(name, req.getParameter("v")))` allows
|
|
10792
|
+
// CRLF injection into the Set-Cookie header. Both the ctor arg[1] (value)
|
|
10793
|
+
// and the addCookie arg[0] (Cookie handle) are flagged so intermediate-var
|
|
10794
|
+
// and inline-ctor shapes both surface.
|
|
10795
|
+
{ method: "Cookie", class: "constructor", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0, 1] },
|
|
10796
|
+
{ method: "addCookie", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0] },
|
|
10770
10797
|
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
10771
10798
|
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
10772
10799
|
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
@@ -12206,11 +12233,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
12206
12233
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
12207
12234
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
12208
12235
|
];
|
|
12236
|
+
var DEFAULT_SINK_SEMANTICS = [
|
|
12237
|
+
{
|
|
12238
|
+
signature: "Jedis#executeCommand",
|
|
12239
|
+
real_class: "db_protocol",
|
|
12240
|
+
overrides: ["command_injection", "code_injection"],
|
|
12241
|
+
note: "Redis wire-protocol serialization, not OS exec"
|
|
12242
|
+
},
|
|
12243
|
+
{
|
|
12244
|
+
signature: "Connection#executeCommand",
|
|
12245
|
+
real_class: "db_protocol",
|
|
12246
|
+
overrides: ["command_injection", "code_injection"],
|
|
12247
|
+
note: "Jedis abstract Connection base"
|
|
12248
|
+
},
|
|
12249
|
+
{
|
|
12250
|
+
signature: "JedisCluster#executeCommand",
|
|
12251
|
+
real_class: "db_protocol",
|
|
12252
|
+
overrides: ["command_injection", "code_injection"],
|
|
12253
|
+
note: "Jedis cluster client"
|
|
12254
|
+
},
|
|
12255
|
+
{
|
|
12256
|
+
signature: "Func1#exec",
|
|
12257
|
+
real_class: "functional_dispatch",
|
|
12258
|
+
overrides: ["command_injection", "code_injection"],
|
|
12259
|
+
note: "RxJava functional dispatch, not OS exec"
|
|
12260
|
+
},
|
|
12261
|
+
{
|
|
12262
|
+
signature: "Action0#call",
|
|
12263
|
+
real_class: "functional_dispatch",
|
|
12264
|
+
overrides: ["command_injection"],
|
|
12265
|
+
note: "RxJava Action0 dispatch"
|
|
12266
|
+
},
|
|
12267
|
+
{
|
|
12268
|
+
signature: "Action1#call",
|
|
12269
|
+
real_class: "functional_dispatch",
|
|
12270
|
+
overrides: ["command_injection"],
|
|
12271
|
+
note: "RxJava Action1 dispatch"
|
|
12272
|
+
},
|
|
12273
|
+
{
|
|
12274
|
+
signature: "Unsafe#defineAnonymousClass",
|
|
12275
|
+
real_class: "jdk_internal",
|
|
12276
|
+
overrides: ["code_injection"],
|
|
12277
|
+
note: "sun.misc.Unsafe JDK-internal reflective bridge"
|
|
12278
|
+
},
|
|
12279
|
+
{
|
|
12280
|
+
signature: "MethodHandle#invokeExact",
|
|
12281
|
+
real_class: "jdk_internal",
|
|
12282
|
+
overrides: ["code_injection"],
|
|
12283
|
+
note: "java.lang.invoke.MethodHandle \u2014 JDK-internal"
|
|
12284
|
+
}
|
|
12285
|
+
];
|
|
12209
12286
|
function getDefaultConfig() {
|
|
12210
12287
|
return {
|
|
12211
12288
|
sources: DEFAULT_SOURCES,
|
|
12212
12289
|
sinks: DEFAULT_SINKS,
|
|
12213
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
12290
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
12291
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
12214
12292
|
};
|
|
12215
12293
|
}
|
|
12216
12294
|
|
|
@@ -12967,6 +13045,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12967
13045
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
12968
13046
|
const existing = sinkMap.get(key);
|
|
12969
13047
|
if (!existing || confidence > existing.confidence) {
|
|
13048
|
+
const receiverType = call.receiver_type;
|
|
13049
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
12970
13050
|
sinkMap.set(key, {
|
|
12971
13051
|
type: pattern.type,
|
|
12972
13052
|
cwe: pattern.cwe,
|
|
@@ -12974,7 +13054,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12974
13054
|
line: call.location.line,
|
|
12975
13055
|
confidence,
|
|
12976
13056
|
method: call.method_name,
|
|
12977
|
-
argPositions: pattern.arg_positions
|
|
13057
|
+
argPositions: pattern.arg_positions,
|
|
13058
|
+
class: simpleClass
|
|
12978
13059
|
});
|
|
12979
13060
|
}
|
|
12980
13061
|
}
|