cognium-dev 3.141.0 → 3.145.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +381 -19
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5668,6 +5668,12 @@ function resolveReceiverType(receiver, context) {
|
|
|
5668
5668
|
}
|
|
5669
5669
|
if (receiver === "super")
|
|
5670
5670
|
return { simpleName: null, fqn: null };
|
|
5671
|
+
const ctorMatch = receiver.match(/^new\s+([A-Za-z_$][\w$.]*)\s*[<(]/);
|
|
5672
|
+
if (ctorMatch) {
|
|
5673
|
+
const ctorClass = ctorMatch[1];
|
|
5674
|
+
const simple = ctorClass.includes(".") ? ctorClass.substring(ctorClass.lastIndexOf(".") + 1) : ctorClass;
|
|
5675
|
+
return resolveFqn(simple, context);
|
|
5676
|
+
}
|
|
5671
5677
|
const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
|
|
5672
5678
|
if (declaredType) {
|
|
5673
5679
|
return resolveFqn(stripGenerics(declaredType), context);
|
|
@@ -10732,6 +10738,8 @@ var DEFAULT_SINKS = [
|
|
|
10732
10738
|
{ method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
10733
10739
|
{ method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10734
10740
|
{ method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
|
|
10741
|
+
{ method: "Cookie", class: "constructor", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0, 1] },
|
|
10742
|
+
{ method: "addCookie", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0] },
|
|
10735
10743
|
{ method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
|
|
10736
10744
|
{ method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
10737
10745
|
{ method: "addAttribute", class: "Model", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
@@ -11668,11 +11676,62 @@ var DEFAULT_SANITIZERS = [
|
|
|
11668
11676
|
{ method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
|
|
11669
11677
|
{ method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
|
|
11670
11678
|
];
|
|
11679
|
+
var DEFAULT_SINK_SEMANTICS = [
|
|
11680
|
+
{
|
|
11681
|
+
signature: "Jedis#executeCommand",
|
|
11682
|
+
real_class: "db_protocol",
|
|
11683
|
+
overrides: ["command_injection", "code_injection"],
|
|
11684
|
+
note: "Redis wire-protocol serialization, not OS exec"
|
|
11685
|
+
},
|
|
11686
|
+
{
|
|
11687
|
+
signature: "Connection#executeCommand",
|
|
11688
|
+
real_class: "db_protocol",
|
|
11689
|
+
overrides: ["command_injection", "code_injection"],
|
|
11690
|
+
note: "Jedis abstract Connection base"
|
|
11691
|
+
},
|
|
11692
|
+
{
|
|
11693
|
+
signature: "JedisCluster#executeCommand",
|
|
11694
|
+
real_class: "db_protocol",
|
|
11695
|
+
overrides: ["command_injection", "code_injection"],
|
|
11696
|
+
note: "Jedis cluster client"
|
|
11697
|
+
},
|
|
11698
|
+
{
|
|
11699
|
+
signature: "Func1#exec",
|
|
11700
|
+
real_class: "functional_dispatch",
|
|
11701
|
+
overrides: ["command_injection", "code_injection"],
|
|
11702
|
+
note: "RxJava functional dispatch, not OS exec"
|
|
11703
|
+
},
|
|
11704
|
+
{
|
|
11705
|
+
signature: "Action0#call",
|
|
11706
|
+
real_class: "functional_dispatch",
|
|
11707
|
+
overrides: ["command_injection"],
|
|
11708
|
+
note: "RxJava Action0 dispatch"
|
|
11709
|
+
},
|
|
11710
|
+
{
|
|
11711
|
+
signature: "Action1#call",
|
|
11712
|
+
real_class: "functional_dispatch",
|
|
11713
|
+
overrides: ["command_injection"],
|
|
11714
|
+
note: "RxJava Action1 dispatch"
|
|
11715
|
+
},
|
|
11716
|
+
{
|
|
11717
|
+
signature: "Unsafe#defineAnonymousClass",
|
|
11718
|
+
real_class: "jdk_internal",
|
|
11719
|
+
overrides: ["code_injection"],
|
|
11720
|
+
note: "sun.misc.Unsafe JDK-internal reflective bridge"
|
|
11721
|
+
},
|
|
11722
|
+
{
|
|
11723
|
+
signature: "MethodHandle#invokeExact",
|
|
11724
|
+
real_class: "jdk_internal",
|
|
11725
|
+
overrides: ["code_injection"],
|
|
11726
|
+
note: "java.lang.invoke.MethodHandle — JDK-internal"
|
|
11727
|
+
}
|
|
11728
|
+
];
|
|
11671
11729
|
function getDefaultConfig() {
|
|
11672
11730
|
return {
|
|
11673
11731
|
sources: DEFAULT_SOURCES,
|
|
11674
11732
|
sinks: DEFAULT_SINKS,
|
|
11675
|
-
sanitizers: DEFAULT_SANITIZERS
|
|
11733
|
+
sanitizers: DEFAULT_SANITIZERS,
|
|
11734
|
+
sinkSemantics: DEFAULT_SINK_SEMANTICS
|
|
11676
11735
|
};
|
|
11677
11736
|
}
|
|
11678
11737
|
var DEFAULT_HEADER_RULES = [
|
|
@@ -12001,7 +12060,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12001
12060
|
severity: "medium",
|
|
12002
12061
|
line: paramLine,
|
|
12003
12062
|
confidence: param.type ? 0.7 : 0.5,
|
|
12004
|
-
in_method: method.name
|
|
12063
|
+
in_method: method.name,
|
|
12064
|
+
...language === "java" ? { variable: param.name } : {}
|
|
12005
12065
|
});
|
|
12006
12066
|
}
|
|
12007
12067
|
}
|
|
@@ -12499,7 +12559,19 @@ function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
|
12499
12559
|
return false;
|
|
12500
12560
|
}
|
|
12501
12561
|
var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
|
|
12502
|
-
|
|
12562
|
+
var JINJA_AUTOESCAPE_TRUE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*True\b/;
|
|
12563
|
+
var JINJA_SELECT_AUTOESCAPE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*select_autoescape\s*\(/;
|
|
12564
|
+
var JINJA_AUTOESCAPE_FALSE_RE = /\bEnvironment\s*\([^)]*\bautoescape\s*=\s*False\b/;
|
|
12565
|
+
function fileHasSafeJinjaEnvironment(sourceLines) {
|
|
12566
|
+
if (!sourceLines || sourceLines.length === 0)
|
|
12567
|
+
return false;
|
|
12568
|
+
const text = sourceLines.join(`
|
|
12569
|
+
`);
|
|
12570
|
+
if (JINJA_AUTOESCAPE_FALSE_RE.test(text))
|
|
12571
|
+
return false;
|
|
12572
|
+
return JINJA_AUTOESCAPE_TRUE_RE.test(text) || JINJA_SELECT_AUTOESCAPE_RE.test(text);
|
|
12573
|
+
}
|
|
12574
|
+
function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
12503
12575
|
if (language !== "python")
|
|
12504
12576
|
return false;
|
|
12505
12577
|
if (pattern.type !== "xss" && pattern.type !== "code_injection")
|
|
@@ -12515,7 +12587,10 @@ function isSafeJinjaRenderCall(call, pattern, language) {
|
|
|
12515
12587
|
}
|
|
12516
12588
|
if (method === "render") {
|
|
12517
12589
|
const receiver = (call.receiver ?? "").trim();
|
|
12518
|
-
|
|
12590
|
+
if (TEMPLATE_LITERAL_RECEIVER_RE.test(receiver))
|
|
12591
|
+
return true;
|
|
12592
|
+
if (fileHasSafeJinjaEnvironment(sourceLines))
|
|
12593
|
+
return true;
|
|
12519
12594
|
}
|
|
12520
12595
|
return false;
|
|
12521
12596
|
}
|
|
@@ -12563,7 +12638,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12563
12638
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
12564
12639
|
continue;
|
|
12565
12640
|
}
|
|
12566
|
-
if (isSafeJinjaRenderCall(call, pattern, language)) {
|
|
12641
|
+
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
12567
12642
|
continue;
|
|
12568
12643
|
}
|
|
12569
12644
|
const location = formatCallLocation(call);
|
|
@@ -12571,6 +12646,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12571
12646
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
12572
12647
|
const existing = sinkMap.get(key);
|
|
12573
12648
|
if (!existing || confidence > existing.confidence) {
|
|
12649
|
+
const receiverType = call.receiver_type;
|
|
12650
|
+
const simpleClass = receiverType ? receiverType.split(".").pop() || undefined : undefined;
|
|
12574
12651
|
sinkMap.set(key, {
|
|
12575
12652
|
type: pattern.type,
|
|
12576
12653
|
cwe: pattern.cwe,
|
|
@@ -12578,7 +12655,8 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12578
12655
|
line: call.location.line,
|
|
12579
12656
|
confidence,
|
|
12580
12657
|
method: call.method_name,
|
|
12581
|
-
argPositions: pattern.arg_positions
|
|
12658
|
+
argPositions: pattern.arg_positions,
|
|
12659
|
+
class: simpleClass
|
|
12582
12660
|
});
|
|
12583
12661
|
}
|
|
12584
12662
|
}
|
|
@@ -13478,12 +13556,12 @@ function formatCallCode(call) {
|
|
|
13478
13556
|
// ../circle-ir/dist/analysis/findings.js
|
|
13479
13557
|
function canSourceReachSink(sourceType, sinkType) {
|
|
13480
13558
|
const sourceToSinkMapping = {
|
|
13481
|
-
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"],
|
|
13559
|
+
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"],
|
|
13482
13560
|
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13483
13561
|
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
13484
13562
|
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary"],
|
|
13485
13563
|
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary"],
|
|
13486
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary"],
|
|
13564
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization"],
|
|
13487
13565
|
io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
|
|
13488
13566
|
env_input: ["command_injection", "path_traversal"],
|
|
13489
13567
|
db_input: ["xss", "sql_injection"],
|
|
@@ -13496,6 +13574,15 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
13496
13574
|
const validSinks = sourceToSinkMapping[sourceType];
|
|
13497
13575
|
return validSinks ? validSinks.includes(sinkType) : false;
|
|
13498
13576
|
}
|
|
13577
|
+
function sourceSemanticsAllowed(source, sinkType) {
|
|
13578
|
+
if (source.constant === true) {
|
|
13579
|
+
return false;
|
|
13580
|
+
}
|
|
13581
|
+
if (source.spi === true) {
|
|
13582
|
+
return sinkType === "code_injection";
|
|
13583
|
+
}
|
|
13584
|
+
return true;
|
|
13585
|
+
}
|
|
13499
13586
|
|
|
13500
13587
|
// ../circle-ir/dist/analysis/findings-instrumentation.js
|
|
13501
13588
|
var instrumentEnabled = false;
|
|
@@ -23602,6 +23689,68 @@ function buildRustTaintedVars(sourceCode, seedVars) {
|
|
|
23602
23689
|
}
|
|
23603
23690
|
return derived;
|
|
23604
23691
|
}
|
|
23692
|
+
function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
23693
|
+
const derived = new Map;
|
|
23694
|
+
const knownTainted = new Set(seedVars);
|
|
23695
|
+
const lines = sourceCode.split(`
|
|
23696
|
+
`);
|
|
23697
|
+
const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
23698
|
+
const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
23699
|
+
const JAVA_KEYWORDS = new Set([
|
|
23700
|
+
"if",
|
|
23701
|
+
"else",
|
|
23702
|
+
"while",
|
|
23703
|
+
"for",
|
|
23704
|
+
"do",
|
|
23705
|
+
"switch",
|
|
23706
|
+
"case",
|
|
23707
|
+
"return",
|
|
23708
|
+
"throw",
|
|
23709
|
+
"try",
|
|
23710
|
+
"catch",
|
|
23711
|
+
"finally",
|
|
23712
|
+
"new",
|
|
23713
|
+
"this",
|
|
23714
|
+
"super",
|
|
23715
|
+
"break",
|
|
23716
|
+
"continue",
|
|
23717
|
+
"default",
|
|
23718
|
+
"class",
|
|
23719
|
+
"interface",
|
|
23720
|
+
"enum"
|
|
23721
|
+
]);
|
|
23722
|
+
let changed = true;
|
|
23723
|
+
let guard = 0;
|
|
23724
|
+
while (changed && guard < lines.length + 2) {
|
|
23725
|
+
changed = false;
|
|
23726
|
+
guard++;
|
|
23727
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
23728
|
+
const line = lines[i2];
|
|
23729
|
+
const trimmed = line.trimStart();
|
|
23730
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
23731
|
+
continue;
|
|
23732
|
+
const declMatch = declRe.exec(line);
|
|
23733
|
+
const assignMatch = !declMatch ? assignRe.exec(line) : null;
|
|
23734
|
+
const m = declMatch ?? assignMatch;
|
|
23735
|
+
if (!m)
|
|
23736
|
+
continue;
|
|
23737
|
+
const lhs = m[1];
|
|
23738
|
+
const rhs = m[2];
|
|
23739
|
+
if (JAVA_KEYWORDS.has(lhs))
|
|
23740
|
+
continue;
|
|
23741
|
+
if (knownTainted.has(lhs))
|
|
23742
|
+
continue;
|
|
23743
|
+
const escaped = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23744
|
+
const ref = [...knownTainted].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(rhs));
|
|
23745
|
+
if (ref) {
|
|
23746
|
+
derived.set(lhs, i2 + 1);
|
|
23747
|
+
knownTainted.add(lhs);
|
|
23748
|
+
changed = true;
|
|
23749
|
+
}
|
|
23750
|
+
}
|
|
23751
|
+
}
|
|
23752
|
+
return derived;
|
|
23753
|
+
}
|
|
23605
23754
|
var BASH_POSITIONAL_PARAMS = new Set(["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"]);
|
|
23606
23755
|
var BASH_UNTRUSTED_ENV_PATTERNS = [
|
|
23607
23756
|
/^USER_INPUT$/i,
|
|
@@ -23982,7 +24131,7 @@ function findBashRealpathPrefixGuardSanitizers(code) {
|
|
|
23982
24131
|
const caseOpen = /^\s*case\s+"?\$\{?\w+\}?"?\s+in\b/;
|
|
23983
24132
|
const esacClose = /^\s*esac\b/;
|
|
23984
24133
|
const armOpener = /^\s*([^)\s][^)]*?)\)/;
|
|
23985
|
-
const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|[\w\-./]+)(?:\/|\*)/;
|
|
24134
|
+
const prefixArm = /^(?:"\$\{?\w+\}?"|"[^"]*"|\/[\w\-./]+|\$\{?\w+\}?|https?:\/\/[\w\-.]+|[\w\-./]+)(?:\/|\*)/;
|
|
23986
24135
|
const catchAllArm = /^(?:\*|\\\*)$/;
|
|
23987
24136
|
let i2 = 0;
|
|
23988
24137
|
while (i2 < lines.length) {
|
|
@@ -24915,10 +25064,13 @@ function findJavaArgvFormExecSanitizers(code) {
|
|
|
24915
25064
|
`);
|
|
24916
25065
|
const argvExecRe = /\.\s*exec\s*\(\s*new\s+String\s*\[\s*\]\s*\{/;
|
|
24917
25066
|
const argvPbRe = /\bnew\s+ProcessBuilder\s*\(\s*new\s+String\s*\[\s*\]\s*\{/;
|
|
25067
|
+
const shellInStringRe = /new\s+String\s*\[\s*\]\s*\{\s*"(?:\/(?:usr\/)?bin\/(?:sh|bash|zsh|ksh|dash)|(?:sh|bash|zsh|ksh|dash)|cmd(?:\.exe)?|powershell(?:\.exe)?|pwsh)"\s*,\s*"(?:-c|\/c|-Command|-command)"/i;
|
|
24918
25068
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
24919
25069
|
const text = lines[i2];
|
|
24920
25070
|
if (!argvExecRe.test(text) && !argvPbRe.test(text))
|
|
24921
25071
|
continue;
|
|
25072
|
+
if (shellInStringRe.test(text))
|
|
25073
|
+
continue;
|
|
24922
25074
|
sanitizers.push({
|
|
24923
25075
|
type: "java_argv_form_exec",
|
|
24924
25076
|
method: "exec",
|
|
@@ -27069,6 +27221,20 @@ function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
|
|
|
27069
27221
|
}
|
|
27070
27222
|
return findings;
|
|
27071
27223
|
}
|
|
27224
|
+
function hasHostAllowlistBeforeSink(taintedVars, lines, sinkLineIdx) {
|
|
27225
|
+
for (let i2 = 0;i2 < sinkLineIdx; i2++) {
|
|
27226
|
+
const t = lines[i2];
|
|
27227
|
+
for (const v of taintedVars) {
|
|
27228
|
+
const containsRe = new RegExp(`\\.\\s*contains\\s*\\(\\s*${v}\\s*\\.\\s*getHost\\s*\\(\\s*\\)\\s*\\)`);
|
|
27229
|
+
if (containsRe.test(t))
|
|
27230
|
+
return true;
|
|
27231
|
+
const equalsRe = new RegExp(`\\b${v}\\s*\\.\\s*getHost\\s*\\(\\s*\\)\\s*\\.\\s*equals(?:IgnoreCase)?\\s*\\(\\s*"[^"]+"\\s*\\)`);
|
|
27232
|
+
if (equalsRe.test(t))
|
|
27233
|
+
return true;
|
|
27234
|
+
}
|
|
27235
|
+
}
|
|
27236
|
+
return false;
|
|
27237
|
+
}
|
|
27072
27238
|
function findJavaUrlOpenStreamSsrfFindings(code, file) {
|
|
27073
27239
|
const findings = [];
|
|
27074
27240
|
if (typeof code !== "string" || code.length === 0)
|
|
@@ -27133,6 +27299,8 @@ function findJavaUrlOpenStreamSsrfFindings(code, file) {
|
|
|
27133
27299
|
}
|
|
27134
27300
|
if (!tainted)
|
|
27135
27301
|
continue;
|
|
27302
|
+
if (hasHostAllowlistBeforeSink(taintedVars, lines, i2))
|
|
27303
|
+
continue;
|
|
27136
27304
|
const key = `${i2 + 1}:${op}`;
|
|
27137
27305
|
if (seen.has(key))
|
|
27138
27306
|
continue;
|
|
@@ -28443,6 +28611,94 @@ function findJsTemplateInjectionSstiFindings(code, file) {
|
|
|
28443
28611
|
return findings;
|
|
28444
28612
|
}
|
|
28445
28613
|
|
|
28614
|
+
// ../circle-ir/dist/analysis/passes/source-semantics-pass.js
|
|
28615
|
+
var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
|
|
28616
|
+
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*$/;
|
|
28617
|
+
var STATIC_FINAL_RE = /^\s*(?:public\s+|private\s+|protected\s+)?static\s+final\s+/;
|
|
28618
|
+
var ENUM_CONST_REF_RE = /=\s*[A-Z][A-Za-z0-9_]*\.[A-Z][A-Z0-9_]*\s*;?\s*$/;
|
|
28619
|
+
function isConstantSource(code) {
|
|
28620
|
+
if (!code)
|
|
28621
|
+
return false;
|
|
28622
|
+
if (CONST_STRING_ASSIGN_RE.test(code))
|
|
28623
|
+
return true;
|
|
28624
|
+
if (STATIC_FINAL_RE.test(code)) {
|
|
28625
|
+
const rhs = code.split("=").slice(1).join("=").trim();
|
|
28626
|
+
if (rhs.length === 0)
|
|
28627
|
+
return false;
|
|
28628
|
+
if (/^"[^"]*"\s*;?\s*$/.test(rhs))
|
|
28629
|
+
return true;
|
|
28630
|
+
if (/^-?\d+(?:\.\d+)?[fFdDlL]?\s*;?\s*$/.test(rhs))
|
|
28631
|
+
return true;
|
|
28632
|
+
if (/^(?:true|false)\s*;?\s*$/.test(rhs))
|
|
28633
|
+
return true;
|
|
28634
|
+
if (/^[A-Za-z_][\w.]*\s*;?\s*$/.test(rhs))
|
|
28635
|
+
return true;
|
|
28636
|
+
return false;
|
|
28637
|
+
}
|
|
28638
|
+
if (ENUM_CONST_REF_RE.test(code))
|
|
28639
|
+
return true;
|
|
28640
|
+
return false;
|
|
28641
|
+
}
|
|
28642
|
+
var SERVICE_LOADER_RE = /\bServiceLoader\.(?:load|loadInstalled|stream)\s*\(/;
|
|
28643
|
+
var CLASS_FOR_NAME_RE = /\bClass\.forName\s*\(/;
|
|
28644
|
+
var META_INF_SERVICES_RE = /getResources?\s*\(\s*"META-INF\/services\//;
|
|
28645
|
+
var SPI_WINDOW = 30;
|
|
28646
|
+
function isSpiSource(source, lines) {
|
|
28647
|
+
const code = source.code;
|
|
28648
|
+
if (!code)
|
|
28649
|
+
return false;
|
|
28650
|
+
if (SERVICE_LOADER_RE.test(code))
|
|
28651
|
+
return true;
|
|
28652
|
+
if (CLASS_FOR_NAME_RE.test(code)) {
|
|
28653
|
+
const start2 = Math.max(0, source.line - 1 - SPI_WINDOW);
|
|
28654
|
+
const end = Math.min(lines.length, source.line - 1 + SPI_WINDOW + 1);
|
|
28655
|
+
for (let i2 = start2;i2 < end; i2++) {
|
|
28656
|
+
if (META_INF_SERVICES_RE.test(lines[i2]))
|
|
28657
|
+
return true;
|
|
28658
|
+
}
|
|
28659
|
+
}
|
|
28660
|
+
return false;
|
|
28661
|
+
}
|
|
28662
|
+
function isDemoPathFile(file) {
|
|
28663
|
+
if (!file)
|
|
28664
|
+
return false;
|
|
28665
|
+
return DEMO_PATH_RE.test(file);
|
|
28666
|
+
}
|
|
28667
|
+
|
|
28668
|
+
class SourceSemanticsPass {
|
|
28669
|
+
name = "source-semantics";
|
|
28670
|
+
category = "security";
|
|
28671
|
+
run(ctx) {
|
|
28672
|
+
const { graph, code } = ctx;
|
|
28673
|
+
const sources = graph.ir.taint.sources;
|
|
28674
|
+
if (sources.length === 0) {
|
|
28675
|
+
return { constantCount: 0, spiCount: 0, demoPathCount: 0 };
|
|
28676
|
+
}
|
|
28677
|
+
const file = graph.ir.meta.file;
|
|
28678
|
+
const demoPath = isDemoPathFile(file);
|
|
28679
|
+
const lines = code.split(`
|
|
28680
|
+
`);
|
|
28681
|
+
let constantCount = 0;
|
|
28682
|
+
let spiCount = 0;
|
|
28683
|
+
let demoPathCount = 0;
|
|
28684
|
+
for (const source of sources) {
|
|
28685
|
+
if (isConstantSource(source.code)) {
|
|
28686
|
+
source.constant = true;
|
|
28687
|
+
constantCount++;
|
|
28688
|
+
}
|
|
28689
|
+
if (isSpiSource(source, lines)) {
|
|
28690
|
+
source.spi = true;
|
|
28691
|
+
spiCount++;
|
|
28692
|
+
}
|
|
28693
|
+
if (demoPath) {
|
|
28694
|
+
source.demoPath = true;
|
|
28695
|
+
demoPathCount++;
|
|
28696
|
+
}
|
|
28697
|
+
}
|
|
28698
|
+
return { constantCount, spiCount, demoPathCount };
|
|
28699
|
+
}
|
|
28700
|
+
}
|
|
28701
|
+
|
|
28446
28702
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
28447
28703
|
var JS_XSS_SANITIZERS = [
|
|
28448
28704
|
/\bDOMPurify\.sanitize\s*\(/,
|
|
@@ -29024,7 +29280,7 @@ class SinkFilterPass {
|
|
|
29024
29280
|
filtered = filtered.filter((sink) => {
|
|
29025
29281
|
if (sink.type !== "command_injection")
|
|
29026
29282
|
return true;
|
|
29027
|
-
if (sink.method !== "ProcessBuilder")
|
|
29283
|
+
if (sink.method !== "ProcessBuilder" && sink.method !== "start")
|
|
29028
29284
|
return true;
|
|
29029
29285
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
29030
29286
|
if (!/\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText))
|
|
@@ -29861,6 +30117,54 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
29861
30117
|
});
|
|
29862
30118
|
}
|
|
29863
30119
|
|
|
30120
|
+
// ../circle-ir/dist/analysis/passes/sink-semantics-pass.js
|
|
30121
|
+
function buildRegistry(entries) {
|
|
30122
|
+
const registry = new Map;
|
|
30123
|
+
for (const entry of entries) {
|
|
30124
|
+
const existing = registry.get(entry.signature);
|
|
30125
|
+
if (existing) {
|
|
30126
|
+
for (const t of entry.overrides)
|
|
30127
|
+
existing.add(t);
|
|
30128
|
+
} else {
|
|
30129
|
+
registry.set(entry.signature, new Set(entry.overrides));
|
|
30130
|
+
}
|
|
30131
|
+
}
|
|
30132
|
+
return registry;
|
|
30133
|
+
}
|
|
30134
|
+
|
|
30135
|
+
class SinkSemanticsPass {
|
|
30136
|
+
name = "sink-semantics";
|
|
30137
|
+
category = "security";
|
|
30138
|
+
run(ctx) {
|
|
30139
|
+
const { graph, config } = ctx;
|
|
30140
|
+
const entries = config.sinkSemantics ?? [];
|
|
30141
|
+
if (entries.length === 0) {
|
|
30142
|
+
return { droppedCount: 0, registrySize: 0 };
|
|
30143
|
+
}
|
|
30144
|
+
const registry = buildRegistry(entries);
|
|
30145
|
+
const sinks = graph.ir.taint.sinks;
|
|
30146
|
+
let droppedCount = 0;
|
|
30147
|
+
const kept = sinks.filter((sink) => {
|
|
30148
|
+
if (!sink.class || !sink.method)
|
|
30149
|
+
return true;
|
|
30150
|
+
const signature = `${sink.class}#${sink.method}`;
|
|
30151
|
+
const overrides = registry.get(signature);
|
|
30152
|
+
if (!overrides)
|
|
30153
|
+
return true;
|
|
30154
|
+
if (overrides.has(sink.type)) {
|
|
30155
|
+
droppedCount++;
|
|
30156
|
+
return false;
|
|
30157
|
+
}
|
|
30158
|
+
return true;
|
|
30159
|
+
});
|
|
30160
|
+
if (droppedCount > 0) {
|
|
30161
|
+
sinks.length = 0;
|
|
30162
|
+
sinks.push(...kept);
|
|
30163
|
+
}
|
|
30164
|
+
return { droppedCount, registrySize: registry.size };
|
|
30165
|
+
}
|
|
30166
|
+
}
|
|
30167
|
+
|
|
29864
30168
|
// ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
|
|
29865
30169
|
class TaintPropagationPass {
|
|
29866
30170
|
name = "taint-propagation";
|
|
@@ -30555,6 +30859,27 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30555
30859
|
}
|
|
30556
30860
|
}
|
|
30557
30861
|
}
|
|
30862
|
+
if (language === "java" && typeof code === "string" && sourcesWithVar.length > 0) {
|
|
30863
|
+
const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
|
|
30864
|
+
const derived = buildJavaTaintedVars(code, seedVars);
|
|
30865
|
+
if (derived.size > 0) {
|
|
30866
|
+
let anchor = sourcesWithVar[0];
|
|
30867
|
+
for (const s of sourcesWithVar) {
|
|
30868
|
+
if (s.line < anchor.line)
|
|
30869
|
+
anchor = s;
|
|
30870
|
+
}
|
|
30871
|
+
const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
|
|
30872
|
+
for (const [varName] of derived) {
|
|
30873
|
+
if (!varName || existingVars.has(varName))
|
|
30874
|
+
continue;
|
|
30875
|
+
sourcesWithVar.push({
|
|
30876
|
+
...anchor,
|
|
30877
|
+
variable: varName
|
|
30878
|
+
});
|
|
30879
|
+
existingVars.add(varName);
|
|
30880
|
+
}
|
|
30881
|
+
}
|
|
30882
|
+
}
|
|
30558
30883
|
const reCache = new Map;
|
|
30559
30884
|
for (const s of sourcesWithVar) {
|
|
30560
30885
|
if (reCache.has(s.variable))
|
|
@@ -30589,6 +30914,8 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30589
30914
|
const re = reCache.get(source.variable);
|
|
30590
30915
|
if (!re || !re.test(expr))
|
|
30591
30916
|
continue;
|
|
30917
|
+
if (!sourceSemanticsAllowed(source, sink.type))
|
|
30918
|
+
continue;
|
|
30592
30919
|
if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
|
|
30593
30920
|
continue;
|
|
30594
30921
|
if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
|
|
@@ -30616,8 +30943,6 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30616
30943
|
}
|
|
30617
30944
|
const sourcesByLine = new Map;
|
|
30618
30945
|
for (const s of sources) {
|
|
30619
|
-
if (s.variable && s.variable.length > 0)
|
|
30620
|
-
continue;
|
|
30621
30946
|
const arr = sourcesByLine.get(s.line) ?? [];
|
|
30622
30947
|
arr.push(s);
|
|
30623
30948
|
sourcesByLine.set(s.line, arr);
|
|
@@ -30631,6 +30956,20 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
30631
30956
|
for (const source of colocSources) {
|
|
30632
30957
|
if (!canSourceReachSink(source.type, sink.type))
|
|
30633
30958
|
continue;
|
|
30959
|
+
if (!sourceSemanticsAllowed(source, sink.type))
|
|
30960
|
+
continue;
|
|
30961
|
+
const sourceVar = source.variable;
|
|
30962
|
+
if (sourceVar && sourceVar.length > 0) {
|
|
30963
|
+
const sinkCode = sink.code;
|
|
30964
|
+
if (!sinkCode) {
|
|
30965
|
+
continue;
|
|
30966
|
+
}
|
|
30967
|
+
const assignMatch = sinkCode.match(/^\s*(?:[A-Za-z_][\w.<>[\]\s,?]*\s+)?[A-Za-z_]\w*\s*=(?!=)\s*/);
|
|
30968
|
+
const rhs = assignMatch ? sinkCode.slice(assignMatch[0].length) : sinkCode;
|
|
30969
|
+
if (new RegExp(`\\b${sourceVar}\\b`).test(rhs)) {
|
|
30970
|
+
continue;
|
|
30971
|
+
}
|
|
30972
|
+
}
|
|
30634
30973
|
if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
|
|
30635
30974
|
continue;
|
|
30636
30975
|
}
|
|
@@ -31219,7 +31558,9 @@ var TIER_1_METHOD_ANNOTATIONS = new Set([
|
|
|
31219
31558
|
"DELETE",
|
|
31220
31559
|
"PATCH",
|
|
31221
31560
|
"HEAD",
|
|
31222
|
-
"OPTIONS"
|
|
31561
|
+
"OPTIONS",
|
|
31562
|
+
"DataBoundConstructor",
|
|
31563
|
+
"DataBoundSetter"
|
|
31223
31564
|
]);
|
|
31224
31565
|
var TIER_1_CLASS_ANNOTATIONS = new Set([
|
|
31225
31566
|
"RestController",
|
|
@@ -31244,7 +31585,13 @@ var TIER_1_BY_SUPERTYPE = new Map([
|
|
|
31244
31585
|
["ChannelInboundHandler", new Set(["channelRead", "channelReadComplete"])],
|
|
31245
31586
|
["ChannelInboundHandlerAdapter", new Set(["channelRead", "channelReadComplete"])],
|
|
31246
31587
|
["ChannelDuplexHandler", new Set(["channelRead", "channelReadComplete"])],
|
|
31247
|
-
["NettyRequestProcessor", new Set(["process"])]
|
|
31588
|
+
["NettyRequestProcessor", new Set(["process"])],
|
|
31589
|
+
["Converter", new Set(["marshal", "unmarshal"])],
|
|
31590
|
+
["SingleValueConverter", new Set(["fromString", "toString"])],
|
|
31591
|
+
["ConverterMatcher", new Set(["marshal", "unmarshal"])],
|
|
31592
|
+
["AbstractReflectionConverter", new Set(["marshal", "unmarshal", "doMarshal", "doUnmarshal"])],
|
|
31593
|
+
["AbstractSingleValueConverter", new Set(["fromString", "toString"])],
|
|
31594
|
+
["AbstractCollectionConverter", new Set(["marshal", "unmarshal"])]
|
|
31248
31595
|
]);
|
|
31249
31596
|
var TIER_3_CLASS_SUFFIXES = [
|
|
31250
31597
|
"Util",
|
|
@@ -35754,6 +36101,14 @@ function isProtocolMandatedCryptoFile(file, code) {
|
|
|
35754
36101
|
}
|
|
35755
36102
|
|
|
35756
36103
|
// ../circle-ir/dist/analysis/passes/scan-secrets-pass.js
|
|
36104
|
+
function applyDemoDowngrade(demoPath, severity, level) {
|
|
36105
|
+
if (!demoPath)
|
|
36106
|
+
return { severity, level };
|
|
36107
|
+
if (severity === "high") {
|
|
36108
|
+
return { severity: "low", level: "note" };
|
|
36109
|
+
}
|
|
36110
|
+
return { severity, level };
|
|
36111
|
+
}
|
|
35757
36112
|
var TEST_PATH_RE3 = /(?:^|[\\/])(?:test|tests|spec|specs|__tests?__|__mocks?__|fixtures?|testdata)(?:[\\/]|$)/i;
|
|
35758
36113
|
var TEST_FILENAME_RE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|_test\.go|_test\.py|Test\.java|Tests\.java)$/i;
|
|
35759
36114
|
function isTestFile(file) {
|
|
@@ -36104,6 +36459,7 @@ class ScanSecretsPass {
|
|
|
36104
36459
|
if (isTestFile(file) || isGeneratedFile(file)) {
|
|
36105
36460
|
return { providerFindings: 0, entropyFindings: 0 };
|
|
36106
36461
|
}
|
|
36462
|
+
const demoPath = DEMO_PATH_RE.test(file);
|
|
36107
36463
|
const lines = ctx.code.split(`
|
|
36108
36464
|
`);
|
|
36109
36465
|
const prior = ctx.getFindings?.() ?? [];
|
|
@@ -36134,14 +36490,15 @@ class ScanSecretsPass {
|
|
|
36134
36490
|
if (seen.has(key))
|
|
36135
36491
|
continue;
|
|
36136
36492
|
seen.add(key);
|
|
36493
|
+
const dg = applyDemoDowngrade(demoPath, pattern.severity, pattern.level);
|
|
36137
36494
|
ctx.addFinding({
|
|
36138
36495
|
id: `hardcoded-credential-${file}-${lineNum}`,
|
|
36139
36496
|
pass: this.name,
|
|
36140
36497
|
category: this.category,
|
|
36141
36498
|
rule_id: "hardcoded-credential",
|
|
36142
36499
|
cwe: "CWE-798",
|
|
36143
|
-
severity:
|
|
36144
|
-
level:
|
|
36500
|
+
severity: dg.severity,
|
|
36501
|
+
level: dg.level,
|
|
36145
36502
|
message: `Hardcoded credential: ${pattern.name} detected`,
|
|
36146
36503
|
file,
|
|
36147
36504
|
line: lineNum,
|
|
@@ -36163,14 +36520,15 @@ class ScanSecretsPass {
|
|
|
36163
36520
|
if (seen.has(key))
|
|
36164
36521
|
continue;
|
|
36165
36522
|
seen.add(key);
|
|
36523
|
+
const dg = applyDemoDowngrade(demoPath, "high", "error");
|
|
36166
36524
|
ctx.addFinding({
|
|
36167
36525
|
id: `hardcoded-credential-${file}-${lineNum}`,
|
|
36168
36526
|
pass: this.name,
|
|
36169
36527
|
category: this.category,
|
|
36170
36528
|
rule_id: "hardcoded-credential",
|
|
36171
36529
|
cwe: "CWE-798",
|
|
36172
|
-
severity:
|
|
36173
|
-
level:
|
|
36530
|
+
severity: dg.severity,
|
|
36531
|
+
level: dg.level,
|
|
36174
36532
|
message: `Hardcoded credential: \`${hit.name}\` assigned a literal value`,
|
|
36175
36533
|
file,
|
|
36176
36534
|
line: lineNum,
|
|
@@ -40791,7 +41149,11 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
40791
41149
|
pipeline.add(new TaintMatcherPass);
|
|
40792
41150
|
pipeline.add(new ConstantPropagationPass(tree));
|
|
40793
41151
|
pipeline.add(new LanguageSourcesPass);
|
|
41152
|
+
if (!disabledPasses.has("source-semantics"))
|
|
41153
|
+
pipeline.add(new SourceSemanticsPass);
|
|
40794
41154
|
pipeline.add(new SinkFilterPass);
|
|
41155
|
+
if (!disabledPasses.has("sink-semantics"))
|
|
41156
|
+
pipeline.add(new SinkSemanticsPass);
|
|
40795
41157
|
pipeline.add(new TaintPropagationPass);
|
|
40796
41158
|
pipeline.add(new InterproceduralPass({
|
|
40797
41159
|
enableEntryPointGate: options.enableEntryPointGate ?? true
|
|
@@ -41719,7 +42081,7 @@ var colors = {
|
|
|
41719
42081
|
};
|
|
41720
42082
|
|
|
41721
42083
|
// src/version.ts
|
|
41722
|
-
var version = "3.
|
|
42084
|
+
var version = "3.145.0";
|
|
41723
42085
|
|
|
41724
42086
|
// src/formatters.ts
|
|
41725
42087
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.145.0",
|
|
4
4
|
"description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@cognium/project-profile-detect": "^1.1.0",
|
|
69
|
-
"circle-ir": "^3.
|
|
69
|
+
"circle-ir": "^3.145.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|