circle-ir 3.140.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 +295 -17
- package/dist/core/circle-ir-core.cjs +142 -11
- package/dist/core/circle-ir-core.js +142 -11
- package/dist/core/extractors/calls.js +104 -18
- 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);
|
|
@@ -6393,19 +6399,43 @@ function resolveReceiverType(receiver, context) {
|
|
|
6393
6399
|
return resolveFqn(simple, context);
|
|
6394
6400
|
}
|
|
6395
6401
|
}
|
|
6396
|
-
const
|
|
6397
|
-
if (
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
if (varType) {
|
|
6402
|
-
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[stripGenerics(varType)]?.[methodName];
|
|
6402
|
+
const chain = splitChainedReceiver(receiver);
|
|
6403
|
+
if (chain) {
|
|
6404
|
+
const prefixType = resolveReceiverType(chain.prefix, context);
|
|
6405
|
+
if (prefixType.simpleName) {
|
|
6406
|
+
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[prefixType.simpleName]?.[chain.methodName];
|
|
6403
6407
|
if (returnType) return resolveFqn(returnType, context);
|
|
6404
6408
|
}
|
|
6405
6409
|
}
|
|
6406
6410
|
return { simpleName: null, fqn: null };
|
|
6407
6411
|
}
|
|
6412
|
+
function splitChainedReceiver(receiver) {
|
|
6413
|
+
if (!receiver.endsWith(")")) return null;
|
|
6414
|
+
let depth = 0;
|
|
6415
|
+
let openIdx = -1;
|
|
6416
|
+
for (let i2 = receiver.length - 1; i2 >= 0; i2--) {
|
|
6417
|
+
const c = receiver[i2];
|
|
6418
|
+
if (c === ")") depth++;
|
|
6419
|
+
else if (c === "(") {
|
|
6420
|
+
depth--;
|
|
6421
|
+
if (depth === 0) {
|
|
6422
|
+
openIdx = i2;
|
|
6423
|
+
break;
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (openIdx <= 0) return null;
|
|
6428
|
+
const beforeParen = receiver.substring(0, openIdx);
|
|
6429
|
+
const dotIdx = beforeParen.lastIndexOf(".");
|
|
6430
|
+
if (dotIdx <= 0) return null;
|
|
6431
|
+
const methodName = beforeParen.substring(dotIdx + 1).trim();
|
|
6432
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(methodName)) return null;
|
|
6433
|
+
const prefix = beforeParen.substring(0, dotIdx).trim();
|
|
6434
|
+
if (!prefix) return null;
|
|
6435
|
+
return { prefix, methodName };
|
|
6436
|
+
}
|
|
6408
6437
|
var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
6438
|
+
// Servlet API (Sprint 91 / #117)
|
|
6409
6439
|
HttpServletRequest: {
|
|
6410
6440
|
getSession: "HttpSession",
|
|
6411
6441
|
getServletContext: "ServletContext",
|
|
@@ -6416,6 +6446,32 @@ var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
|
6416
6446
|
},
|
|
6417
6447
|
ServletContext: {
|
|
6418
6448
|
getRequestDispatcher: "RequestDispatcher"
|
|
6449
|
+
},
|
|
6450
|
+
// JAXP DOM / SAX / XPath / Transformer / StAX factories (Sprint 92 / #189)
|
|
6451
|
+
DocumentBuilderFactory: {
|
|
6452
|
+
newInstance: "DocumentBuilderFactory",
|
|
6453
|
+
newDocumentBuilder: "DocumentBuilder"
|
|
6454
|
+
},
|
|
6455
|
+
SAXParserFactory: {
|
|
6456
|
+
newInstance: "SAXParserFactory",
|
|
6457
|
+
newSAXParser: "SAXParser"
|
|
6458
|
+
},
|
|
6459
|
+
SAXParser: {
|
|
6460
|
+
getXMLReader: "XMLReader"
|
|
6461
|
+
},
|
|
6462
|
+
XPathFactory: {
|
|
6463
|
+
newInstance: "XPathFactory",
|
|
6464
|
+
newXPath: "XPath"
|
|
6465
|
+
},
|
|
6466
|
+
TransformerFactory: {
|
|
6467
|
+
newInstance: "TransformerFactory",
|
|
6468
|
+
newTransformer: "Transformer"
|
|
6469
|
+
},
|
|
6470
|
+
XMLInputFactory: {
|
|
6471
|
+
newInstance: "XMLInputFactory",
|
|
6472
|
+
newFactory: "XMLInputFactory",
|
|
6473
|
+
createXMLStreamReader: "XMLStreamReader",
|
|
6474
|
+
createXMLEventReader: "XMLEventReader"
|
|
6419
6475
|
}
|
|
6420
6476
|
};
|
|
6421
6477
|
function resolveFqn(simpleName, context) {
|
|
@@ -10059,12 +10115,25 @@ function loadSinkConfigs(configs) {
|
|
|
10059
10115
|
}
|
|
10060
10116
|
return { sinks, sanitizers };
|
|
10061
10117
|
}
|
|
10062
|
-
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 = []) {
|
|
10063
10128
|
const sourceConfigs = sourceContents.map((c) => parseConfig(c));
|
|
10064
10129
|
const sinkConfigs = sinkContents.map((c) => parseConfig(c));
|
|
10130
|
+
const sinkSemanticsConfigs = sinkSemanticsContents.map(
|
|
10131
|
+
(c) => parseConfig(c)
|
|
10132
|
+
);
|
|
10065
10133
|
const sources = loadSourceConfigs(sourceConfigs);
|
|
10066
10134
|
const { sinks, sanitizers } = loadSinkConfigs(sinkConfigs);
|
|
10067
|
-
|
|
10135
|
+
const sinkSemantics = loadSinkSemanticsConfigs(sinkSemanticsConfigs);
|
|
10136
|
+
return { sources, sinks, sanitizers, sinkSemantics };
|
|
10068
10137
|
}
|
|
10069
10138
|
var DEFAULT_SOURCES = [
|
|
10070
10139
|
// HTTP Sources (Servlet API)
|
|
@@ -10783,6 +10852,14 @@ var DEFAULT_SINKS = [
|
|
|
10783
10852
|
// reflection remains a downstream concern of body-writing sinks.
|
|
10784
10853
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10785
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] },
|
|
10786
10863
|
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
10787
10864
|
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
10788
10865
|
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
@@ -12222,11 +12299,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
12222
12299
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
12223
12300
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
12224
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
|
+
];
|
|
12225
12352
|
function getDefaultConfig() {
|
|
12226
12353
|
return {
|
|
12227
12354
|
sources: DEFAULT_SOURCES,
|
|
12228
12355
|
sinks: DEFAULT_SINKS,
|
|
12229
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
12356
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
12357
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
12230
12358
|
};
|
|
12231
12359
|
}
|
|
12232
12360
|
|
|
@@ -12983,6 +13111,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12983
13111
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
12984
13112
|
const existing = sinkMap.get(key);
|
|
12985
13113
|
if (!existing || confidence > existing.confidence) {
|
|
13114
|
+
const receiverType = call.receiver_type;
|
|
13115
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
12986
13116
|
sinkMap.set(key, {
|
|
12987
13117
|
type: pattern.type,
|
|
12988
13118
|
cwe: pattern.cwe,
|
|
@@ -12990,7 +13120,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12990
13120
|
line: call.location.line,
|
|
12991
13121
|
confidence,
|
|
12992
13122
|
method: call.method_name,
|
|
12993
|
-
argPositions: pattern.arg_positions
|
|
13123
|
+
argPositions: pattern.arg_positions,
|
|
13124
|
+
class: simpleClass
|
|
12994
13125
|
});
|
|
12995
13126
|
}
|
|
12996
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);
|
|
@@ -6327,19 +6333,43 @@ function resolveReceiverType(receiver, context) {
|
|
|
6327
6333
|
return resolveFqn(simple, context);
|
|
6328
6334
|
}
|
|
6329
6335
|
}
|
|
6330
|
-
const
|
|
6331
|
-
if (
|
|
6332
|
-
const
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
if (varType) {
|
|
6336
|
-
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[stripGenerics(varType)]?.[methodName];
|
|
6336
|
+
const chain = splitChainedReceiver(receiver);
|
|
6337
|
+
if (chain) {
|
|
6338
|
+
const prefixType = resolveReceiverType(chain.prefix, context);
|
|
6339
|
+
if (prefixType.simpleName) {
|
|
6340
|
+
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[prefixType.simpleName]?.[chain.methodName];
|
|
6337
6341
|
if (returnType) return resolveFqn(returnType, context);
|
|
6338
6342
|
}
|
|
6339
6343
|
}
|
|
6340
6344
|
return { simpleName: null, fqn: null };
|
|
6341
6345
|
}
|
|
6346
|
+
function splitChainedReceiver(receiver) {
|
|
6347
|
+
if (!receiver.endsWith(")")) return null;
|
|
6348
|
+
let depth = 0;
|
|
6349
|
+
let openIdx = -1;
|
|
6350
|
+
for (let i2 = receiver.length - 1; i2 >= 0; i2--) {
|
|
6351
|
+
const c = receiver[i2];
|
|
6352
|
+
if (c === ")") depth++;
|
|
6353
|
+
else if (c === "(") {
|
|
6354
|
+
depth--;
|
|
6355
|
+
if (depth === 0) {
|
|
6356
|
+
openIdx = i2;
|
|
6357
|
+
break;
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
6361
|
+
if (openIdx <= 0) return null;
|
|
6362
|
+
const beforeParen = receiver.substring(0, openIdx);
|
|
6363
|
+
const dotIdx = beforeParen.lastIndexOf(".");
|
|
6364
|
+
if (dotIdx <= 0) return null;
|
|
6365
|
+
const methodName = beforeParen.substring(dotIdx + 1).trim();
|
|
6366
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(methodName)) return null;
|
|
6367
|
+
const prefix = beforeParen.substring(0, dotIdx).trim();
|
|
6368
|
+
if (!prefix) return null;
|
|
6369
|
+
return { prefix, methodName };
|
|
6370
|
+
}
|
|
6342
6371
|
var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
6372
|
+
// Servlet API (Sprint 91 / #117)
|
|
6343
6373
|
HttpServletRequest: {
|
|
6344
6374
|
getSession: "HttpSession",
|
|
6345
6375
|
getServletContext: "ServletContext",
|
|
@@ -6350,6 +6380,32 @@ var JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
|
6350
6380
|
},
|
|
6351
6381
|
ServletContext: {
|
|
6352
6382
|
getRequestDispatcher: "RequestDispatcher"
|
|
6383
|
+
},
|
|
6384
|
+
// JAXP DOM / SAX / XPath / Transformer / StAX factories (Sprint 92 / #189)
|
|
6385
|
+
DocumentBuilderFactory: {
|
|
6386
|
+
newInstance: "DocumentBuilderFactory",
|
|
6387
|
+
newDocumentBuilder: "DocumentBuilder"
|
|
6388
|
+
},
|
|
6389
|
+
SAXParserFactory: {
|
|
6390
|
+
newInstance: "SAXParserFactory",
|
|
6391
|
+
newSAXParser: "SAXParser"
|
|
6392
|
+
},
|
|
6393
|
+
SAXParser: {
|
|
6394
|
+
getXMLReader: "XMLReader"
|
|
6395
|
+
},
|
|
6396
|
+
XPathFactory: {
|
|
6397
|
+
newInstance: "XPathFactory",
|
|
6398
|
+
newXPath: "XPath"
|
|
6399
|
+
},
|
|
6400
|
+
TransformerFactory: {
|
|
6401
|
+
newInstance: "TransformerFactory",
|
|
6402
|
+
newTransformer: "Transformer"
|
|
6403
|
+
},
|
|
6404
|
+
XMLInputFactory: {
|
|
6405
|
+
newInstance: "XMLInputFactory",
|
|
6406
|
+
newFactory: "XMLInputFactory",
|
|
6407
|
+
createXMLStreamReader: "XMLStreamReader",
|
|
6408
|
+
createXMLEventReader: "XMLEventReader"
|
|
6353
6409
|
}
|
|
6354
6410
|
};
|
|
6355
6411
|
function resolveFqn(simpleName, context) {
|
|
@@ -9993,12 +10049,25 @@ function loadSinkConfigs(configs) {
|
|
|
9993
10049
|
}
|
|
9994
10050
|
return { sinks, sanitizers };
|
|
9995
10051
|
}
|
|
9996
|
-
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 = []) {
|
|
9997
10062
|
const sourceConfigs = sourceContents.map((c) => parseConfig(c));
|
|
9998
10063
|
const sinkConfigs = sinkContents.map((c) => parseConfig(c));
|
|
10064
|
+
const sinkSemanticsConfigs = sinkSemanticsContents.map(
|
|
10065
|
+
(c) => parseConfig(c)
|
|
10066
|
+
);
|
|
9999
10067
|
const sources = loadSourceConfigs(sourceConfigs);
|
|
10000
10068
|
const { sinks, sanitizers } = loadSinkConfigs(sinkConfigs);
|
|
10001
|
-
|
|
10069
|
+
const sinkSemantics = loadSinkSemanticsConfigs(sinkSemanticsConfigs);
|
|
10070
|
+
return { sources, sinks, sanitizers, sinkSemantics };
|
|
10002
10071
|
}
|
|
10003
10072
|
var DEFAULT_SOURCES = [
|
|
10004
10073
|
// HTTP Sources (Servlet API)
|
|
@@ -10717,6 +10786,14 @@ var DEFAULT_SINKS = [
|
|
|
10717
10786
|
// reflection remains a downstream concern of body-writing sinks.
|
|
10718
10787
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10719
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] },
|
|
10720
10797
|
// Note: `sendRedirect` is primarily classified as `ssrf` / open-redirect
|
|
10721
10798
|
// (CWE-601) further down — see entry near line 1195. CRLF via Location
|
|
10722
10799
|
// header is a secondary concern; keeping the canonical SSRF entry avoids
|
|
@@ -12156,11 +12233,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
12156
12233
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
12157
12234
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
12158
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
|
+
];
|
|
12159
12286
|
function getDefaultConfig() {
|
|
12160
12287
|
return {
|
|
12161
12288
|
sources: DEFAULT_SOURCES,
|
|
12162
12289
|
sinks: DEFAULT_SINKS,
|
|
12163
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
12290
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
12291
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
12164
12292
|
};
|
|
12165
12293
|
}
|
|
12166
12294
|
|
|
@@ -12917,6 +13045,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12917
13045
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
12918
13046
|
const existing = sinkMap.get(key);
|
|
12919
13047
|
if (!existing || confidence > existing.confidence) {
|
|
13048
|
+
const receiverType = call.receiver_type;
|
|
13049
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || void 0 : void 0;
|
|
12920
13050
|
sinkMap.set(key, {
|
|
12921
13051
|
type: pattern.type,
|
|
12922
13052
|
cwe: pattern.cwe,
|
|
@@ -12924,7 +13054,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12924
13054
|
line: call.location.line,
|
|
12925
13055
|
confidence,
|
|
12926
13056
|
method: call.method_name,
|
|
12927
|
-
argPositions: pattern.arg_positions
|
|
13057
|
+
argPositions: pattern.arg_positions,
|
|
13058
|
+
class: simpleClass
|
|
12928
13059
|
});
|
|
12929
13060
|
}
|
|
12930
13061
|
}
|
|
@@ -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) ??
|
|
@@ -845,22 +859,23 @@ function resolveReceiverType(receiver, context) {
|
|
|
845
859
|
return resolveFqn(simple, context);
|
|
846
860
|
}
|
|
847
861
|
}
|
|
848
|
-
// Chained receiver: `<
|
|
849
|
-
// servlet factory map so sink patterns keyed on the returned class
|
|
850
|
-
// `HttpSession.setAttribute` for trust_boundary
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
862
|
+
// Chained receiver: `<prefix>.<method>(...)` — resolve the return type via
|
|
863
|
+
// the servlet/JAXP factory map so sink patterns keyed on the returned class
|
|
864
|
+
// (e.g. `HttpSession.setAttribute` for trust_boundary, `DocumentBuilder.parse`
|
|
865
|
+
// for xxe) match receivers built from chained factory calls like
|
|
866
|
+
// `req.getSession().setAttribute(...)` (#117 Sprint 91) or
|
|
867
|
+
// `DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(...)`
|
|
868
|
+
// (#189 Sprint 92 JAXP factory chains).
|
|
869
|
+
//
|
|
870
|
+
// The prefix can itself be a chained call: recursion terminates on a bare
|
|
871
|
+
// identifier (local var / param / field) or a static class reference. Each
|
|
872
|
+
// step's return type is looked up in JAVA_CHAINED_FACTORY_RETURN_TYPES so
|
|
873
|
+
// multi-level factory chains like `X.newInstance().newY()` resolve to Y.
|
|
874
|
+
const chain = splitChainedReceiver(receiver);
|
|
875
|
+
if (chain) {
|
|
876
|
+
const prefixType = resolveReceiverType(chain.prefix, context);
|
|
877
|
+
if (prefixType.simpleName) {
|
|
878
|
+
const returnType = JAVA_CHAINED_FACTORY_RETURN_TYPES[prefixType.simpleName]?.[chain.methodName];
|
|
864
879
|
if (returnType)
|
|
865
880
|
return resolveFqn(returnType, context);
|
|
866
881
|
}
|
|
@@ -868,7 +883,45 @@ function resolveReceiverType(receiver, context) {
|
|
|
868
883
|
return { simpleName: null, fqn: null };
|
|
869
884
|
}
|
|
870
885
|
/**
|
|
871
|
-
*
|
|
886
|
+
* Split a chained receiver expression into `<prefix>.<methodName>(...)`.
|
|
887
|
+
* Walks the string from the right to find the matching open-paren of the
|
|
888
|
+
* outer call, then finds the last `.` before it to isolate the method name
|
|
889
|
+
* and prefix expression. Returns `null` when the receiver is not a method
|
|
890
|
+
* invocation shape (bare identifier, dotted class name, `this.field`, etc.).
|
|
891
|
+
*/
|
|
892
|
+
function splitChainedReceiver(receiver) {
|
|
893
|
+
if (!receiver.endsWith(')'))
|
|
894
|
+
return null;
|
|
895
|
+
let depth = 0;
|
|
896
|
+
let openIdx = -1;
|
|
897
|
+
for (let i = receiver.length - 1; i >= 0; i--) {
|
|
898
|
+
const c = receiver[i];
|
|
899
|
+
if (c === ')')
|
|
900
|
+
depth++;
|
|
901
|
+
else if (c === '(') {
|
|
902
|
+
depth--;
|
|
903
|
+
if (depth === 0) {
|
|
904
|
+
openIdx = i;
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (openIdx <= 0)
|
|
910
|
+
return null;
|
|
911
|
+
const beforeParen = receiver.substring(0, openIdx);
|
|
912
|
+
const dotIdx = beforeParen.lastIndexOf('.');
|
|
913
|
+
if (dotIdx <= 0)
|
|
914
|
+
return null;
|
|
915
|
+
const methodName = beforeParen.substring(dotIdx + 1).trim();
|
|
916
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(methodName))
|
|
917
|
+
return null;
|
|
918
|
+
const prefix = beforeParen.substring(0, dotIdx).trim();
|
|
919
|
+
if (!prefix)
|
|
920
|
+
return null;
|
|
921
|
+
return { prefix, methodName };
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Chained factory-method return types for Java servlet / JAXP / JSP APIs.
|
|
872
925
|
*
|
|
873
926
|
* Keyed as `receiverType -> methodName -> returnType`. Used by
|
|
874
927
|
* `resolveReceiverType` to pin the type of a receiver written as
|
|
@@ -876,11 +929,18 @@ function resolveReceiverType(receiver, context) {
|
|
|
876
929
|
* recognise `getSession()` as producing `HttpSession` and thus fire the
|
|
877
930
|
* class-scoped `HttpSession.setAttribute` trust_boundary sink pattern.
|
|
878
931
|
*
|
|
879
|
-
*
|
|
932
|
+
* The `resolveReceiverType` walker is recursive (via `splitChainedReceiver`),
|
|
933
|
+
* so multi-level chains like
|
|
934
|
+
* `DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(...)`
|
|
935
|
+
* resolve to `DocumentBuilder` and fire the class-scoped
|
|
936
|
+
* `DocumentBuilder.parse` xxe sink pattern (#189 Sprint 92).
|
|
937
|
+
*
|
|
938
|
+
* Deliberately narrow: only well-known JDK factory APIs where the return
|
|
880
939
|
* type is defined by the interface contract are listed. Application-level
|
|
881
940
|
* factories (`someService.getFoo()`) are still unresolved.
|
|
882
941
|
*/
|
|
883
942
|
const JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
943
|
+
// Servlet API (Sprint 91 / #117)
|
|
884
944
|
HttpServletRequest: {
|
|
885
945
|
getSession: 'HttpSession',
|
|
886
946
|
getServletContext: 'ServletContext',
|
|
@@ -892,6 +952,32 @@ const JAVA_CHAINED_FACTORY_RETURN_TYPES = {
|
|
|
892
952
|
ServletContext: {
|
|
893
953
|
getRequestDispatcher: 'RequestDispatcher',
|
|
894
954
|
},
|
|
955
|
+
// JAXP DOM / SAX / XPath / Transformer / StAX factories (Sprint 92 / #189)
|
|
956
|
+
DocumentBuilderFactory: {
|
|
957
|
+
newInstance: 'DocumentBuilderFactory',
|
|
958
|
+
newDocumentBuilder: 'DocumentBuilder',
|
|
959
|
+
},
|
|
960
|
+
SAXParserFactory: {
|
|
961
|
+
newInstance: 'SAXParserFactory',
|
|
962
|
+
newSAXParser: 'SAXParser',
|
|
963
|
+
},
|
|
964
|
+
SAXParser: {
|
|
965
|
+
getXMLReader: 'XMLReader',
|
|
966
|
+
},
|
|
967
|
+
XPathFactory: {
|
|
968
|
+
newInstance: 'XPathFactory',
|
|
969
|
+
newXPath: 'XPath',
|
|
970
|
+
},
|
|
971
|
+
TransformerFactory: {
|
|
972
|
+
newInstance: 'TransformerFactory',
|
|
973
|
+
newTransformer: 'Transformer',
|
|
974
|
+
},
|
|
975
|
+
XMLInputFactory: {
|
|
976
|
+
newInstance: 'XMLInputFactory',
|
|
977
|
+
newFactory: 'XMLInputFactory',
|
|
978
|
+
createXMLStreamReader: 'XMLStreamReader',
|
|
979
|
+
createXMLEventReader: 'XMLEventReader',
|
|
980
|
+
},
|
|
895
981
|
};
|
|
896
982
|
/**
|
|
897
983
|
* Look up the FQN of a simple type name in the file's imports map.
|