circle-ir 3.141.0 → 3.144.3
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/language-sources-pass.d.ts +19 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +133 -3
- package/dist/analysis/passes/language-sources-pass.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 +91 -10
- 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 +55 -3
- 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 +363 -15
- package/dist/core/circle-ir-core.cjs +112 -8
- package/dist/core/circle-ir-core.js +112 -8
- 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
|
@@ -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
|
|
|
@@ -12499,7 +12577,20 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12499
12577
|
line: paramLine,
|
|
12500
12578
|
confidence: param.type ? 0.7 : 0.5,
|
|
12501
12579
|
// Lower confidence for untyped params
|
|
12502
|
-
in_method: method.name
|
|
12580
|
+
in_method: method.name,
|
|
12581
|
+
// cognium-dev #220 — expose the parameter name to variable-scan flow
|
|
12582
|
+
// detection so uses of the param (including through concat-derived
|
|
12583
|
+
// aliases via `buildJavaTaintedVars`) can be bridged to sinks.
|
|
12584
|
+
//
|
|
12585
|
+
// Java-only: the Python/Rust alias-expansion branches in
|
|
12586
|
+
// taint-propagation-pass.ts use the earliest sourcesWithVar entry
|
|
12587
|
+
// as the anchor for synthetic derived sources. Adding
|
|
12588
|
+
// interprocedural_param to sourcesWithVar in those languages
|
|
12589
|
+
// would cause the anchor to become the low-confidence
|
|
12590
|
+
// interprocedural_param at the method decl line instead of the
|
|
12591
|
+
// real HTTP source, breaking downstream sink-type filters
|
|
12592
|
+
// (regressed #78, #92.1, #105 FP-31, #215 recall).
|
|
12593
|
+
...language === "java" ? { variable: param.name } : {}
|
|
12503
12594
|
});
|
|
12504
12595
|
}
|
|
12505
12596
|
}
|
|
@@ -12963,7 +13054,16 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
|
12963
13054
|
return false;
|
|
12964
13055
|
}
|
|
12965
13056
|
var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
|
|
12966
|
-
|
|
13057
|
+
var JINJA_AUTOESCAPE_TRUE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*True\b/;
|
|
13058
|
+
var JINJA_SELECT_AUTOESCAPE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*select_autoescape\s*\(/;
|
|
13059
|
+
var JINJA_AUTOESCAPE_FALSE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*False\b/;
|
|
13060
|
+
function fileHasSafeJinjaEnvironment(sourceLines) {
|
|
13061
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
13062
|
+
const text = sourceLines.join("\n");
|
|
13063
|
+
if (JINJA_AUTOESCAPE_FALSE_RE.test(text)) return false;
|
|
13064
|
+
return JINJA_AUTOESCAPE_TRUE_RE.test(text) || JINJA_SELECT_AUTOESCAPE_RE.test(text);
|
|
13065
|
+
}
|
|
13066
|
+
function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
12967
13067
|
if (language !== "python") return false;
|
|
12968
13068
|
if (pattern.type !== "xss" && pattern.type !== "code_injection") return false;
|
|
12969
13069
|
const method = call.method_name;
|
|
@@ -12977,7 +13077,8 @@ function isSafeJinjaRenderCall(call, pattern, language) {
|
|
|
12977
13077
|
}
|
|
12978
13078
|
if (method === "render") {
|
|
12979
13079
|
const receiver = (call.receiver ?? "").trim();
|
|
12980
|
-
|
|
13080
|
+
if (TEMPLATE_LITERAL_RECEIVER_RE.test(receiver)) return true;
|
|
13081
|
+
if (fileHasSafeJinjaEnvironment(sourceLines)) return true;
|
|
12981
13082
|
}
|
|
12982
13083
|
return false;
|
|
12983
13084
|
}
|
|
@@ -13025,7 +13126,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13025
13126
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
13026
13127
|
continue;
|
|
13027
13128
|
}
|
|
13028
|
-
if (isSafeJinjaRenderCall(call, pattern, language)) {
|
|
13129
|
+
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
13029
13130
|
continue;
|
|
13030
13131
|
}
|
|
13031
13132
|
const location = formatCallLocation(call);
|
|
@@ -13033,6 +13134,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13033
13134
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
13034
13135
|
const existing = sinkMap.get(key);
|
|
13035
13136
|
if (!existing || confidence > existing.confidence) {
|
|
13137
|
+
const receiverType = call.receiver_type;
|
|
13138
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
13036
13139
|
sinkMap.set(key, {
|
|
13037
13140
|
type: pattern.type,
|
|
13038
13141
|
cwe: pattern.cwe,
|
|
@@ -13040,7 +13143,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13040
13143
|
line: call.location.line,
|
|
13041
13144
|
confidence,
|
|
13042
13145
|
method: call.method_name,
|
|
13043
|
-
argPositions: pattern.arg_positions
|
|
13146
|
+
argPositions: pattern.arg_positions,
|
|
13147
|
+
class: simpleClass
|
|
13044
13148
|
});
|
|
13045
13149
|
}
|
|
13046
13150
|
}
|
|
@@ -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
|
|
|
@@ -12433,7 +12511,20 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12433
12511
|
line: paramLine,
|
|
12434
12512
|
confidence: param.type ? 0.7 : 0.5,
|
|
12435
12513
|
// Lower confidence for untyped params
|
|
12436
|
-
in_method: method.name
|
|
12514
|
+
in_method: method.name,
|
|
12515
|
+
// cognium-dev #220 — expose the parameter name to variable-scan flow
|
|
12516
|
+
// detection so uses of the param (including through concat-derived
|
|
12517
|
+
// aliases via `buildJavaTaintedVars`) can be bridged to sinks.
|
|
12518
|
+
//
|
|
12519
|
+
// Java-only: the Python/Rust alias-expansion branches in
|
|
12520
|
+
// taint-propagation-pass.ts use the earliest sourcesWithVar entry
|
|
12521
|
+
// as the anchor for synthetic derived sources. Adding
|
|
12522
|
+
// interprocedural_param to sourcesWithVar in those languages
|
|
12523
|
+
// would cause the anchor to become the low-confidence
|
|
12524
|
+
// interprocedural_param at the method decl line instead of the
|
|
12525
|
+
// real HTTP source, breaking downstream sink-type filters
|
|
12526
|
+
// (regressed #78, #92.1, #105 FP-31, #215 recall).
|
|
12527
|
+
...language === "java" ? { variable: param.name } : {}
|
|
12437
12528
|
});
|
|
12438
12529
|
}
|
|
12439
12530
|
}
|
|
@@ -12897,7 +12988,16 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
|
12897
12988
|
return false;
|
|
12898
12989
|
}
|
|
12899
12990
|
var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
|
|
12900
|
-
|
|
12991
|
+
var JINJA_AUTOESCAPE_TRUE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*True\b/;
|
|
12992
|
+
var JINJA_SELECT_AUTOESCAPE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*select_autoescape\s*\(/;
|
|
12993
|
+
var JINJA_AUTOESCAPE_FALSE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*False\b/;
|
|
12994
|
+
function fileHasSafeJinjaEnvironment(sourceLines) {
|
|
12995
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
12996
|
+
const text = sourceLines.join("\n");
|
|
12997
|
+
if (JINJA_AUTOESCAPE_FALSE_RE.test(text)) return false;
|
|
12998
|
+
return JINJA_AUTOESCAPE_TRUE_RE.test(text) || JINJA_SELECT_AUTOESCAPE_RE.test(text);
|
|
12999
|
+
}
|
|
13000
|
+
function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
12901
13001
|
if (language !== "python") return false;
|
|
12902
13002
|
if (pattern.type !== "xss" && pattern.type !== "code_injection") return false;
|
|
12903
13003
|
const method = call.method_name;
|
|
@@ -12911,7 +13011,8 @@ function isSafeJinjaRenderCall(call, pattern, language) {
|
|
|
12911
13011
|
}
|
|
12912
13012
|
if (method === "render") {
|
|
12913
13013
|
const receiver = (call.receiver ?? "").trim();
|
|
12914
|
-
|
|
13014
|
+
if (TEMPLATE_LITERAL_RECEIVER_RE.test(receiver)) return true;
|
|
13015
|
+
if (fileHasSafeJinjaEnvironment(sourceLines)) return true;
|
|
12915
13016
|
}
|
|
12916
13017
|
return false;
|
|
12917
13018
|
}
|
|
@@ -12959,7 +13060,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12959
13060
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
12960
13061
|
continue;
|
|
12961
13062
|
}
|
|
12962
|
-
if (isSafeJinjaRenderCall(call, pattern, language)) {
|
|
13063
|
+
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
12963
13064
|
continue;
|
|
12964
13065
|
}
|
|
12965
13066
|
const location = formatCallLocation(call);
|
|
@@ -12967,6 +13068,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12967
13068
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
12968
13069
|
const existing = sinkMap.get(key);
|
|
12969
13070
|
if (!existing || confidence > existing.confidence) {
|
|
13071
|
+
const receiverType = call.receiver_type;
|
|
13072
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
12970
13073
|
sinkMap.set(key, {
|
|
12971
13074
|
type: pattern.type,
|
|
12972
13075
|
cwe: pattern.cwe,
|
|
@@ -12974,7 +13077,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12974
13077
|
line: call.location.line,
|
|
12975
13078
|
confidence,
|
|
12976
13079
|
method: call.method_name,
|
|
12977
|
-
argPositions: pattern.arg_positions
|
|
13080
|
+
argPositions: pattern.arg_positions,
|
|
13081
|
+
class: simpleClass
|
|
12978
13082
|
});
|
|
12979
13083
|
}
|
|
12980
13084
|
}
|
|
@@ -828,6 +828,20 @@ function resolveReceiverType(receiver, context) {
|
|
|
828
828
|
// `super` — defer; cannot determine parent class without hierarchy
|
|
829
829
|
if (receiver === 'super')
|
|
830
830
|
return { simpleName: null, fqn: null };
|
|
831
|
+
// Constructor receiver: `new X(...)` or `new pkg.X(...)` — resolves to the
|
|
832
|
+
// constructed class. Enables sink matching on inline receivers built via
|
|
833
|
+
// `new Yaml().load(taint)` (#189 Sprint 92 SnakeYAML) or
|
|
834
|
+
// `new ObjectMapper().readValue(taint)` (#189 Sprint 92 Jackson). Nested
|
|
835
|
+
// constructor arguments are ignored — only the outer class name matters
|
|
836
|
+
// for method-receiver resolution.
|
|
837
|
+
const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
|
|
838
|
+
if (ctorMatch) {
|
|
839
|
+
const ctorClass = ctorMatch[1];
|
|
840
|
+
const simple = ctorClass.includes('.')
|
|
841
|
+
? ctorClass.substring(ctorClass.lastIndexOf('.') + 1)
|
|
842
|
+
: ctorClass;
|
|
843
|
+
return resolveFqn(simple, context);
|
|
844
|
+
}
|
|
831
845
|
// Local variable, parameter, or field declared in this file
|
|
832
846
|
const declaredType = context.localVarTypes.get(receiver) ??
|
|
833
847
|
context.paramTypes.get(receiver) ??
|