cognium-dev 4.11.0 → 4.13.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/README.md +14 -21
- package/dist/cli.js +903 -60
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -9055,6 +9055,7 @@ function buildCSharpDFG(tree, cache) {
|
|
|
9055
9055
|
currentScope(scopeStack).set(name2, def.id);
|
|
9056
9056
|
}
|
|
9057
9057
|
}
|
|
9058
|
+
const plainTargets = [];
|
|
9058
9059
|
for (const asn of findNodes(body2, "assignment_expression")) {
|
|
9059
9060
|
const left = asn.childForFieldName("left");
|
|
9060
9061
|
if (left?.type === "identifier") {
|
|
@@ -9062,9 +9063,17 @@ function buildCSharpDFG(tree, cache) {
|
|
|
9062
9063
|
const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
|
|
9063
9064
|
defs.push(def);
|
|
9064
9065
|
currentScope(scopeStack).set(name2, def.id);
|
|
9066
|
+
if (asn.children.some((c) => c?.type === "=")) {
|
|
9067
|
+
plainTargets.push({ name: name2, line: left.startPosition.row + 1 });
|
|
9068
|
+
}
|
|
9065
9069
|
}
|
|
9066
9070
|
}
|
|
9067
9071
|
const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
|
|
9072
|
+
for (const target of plainTargets) {
|
|
9073
|
+
const idx = bodyUses.uses.findIndex((u) => u.variable === target.name && u.line === target.line);
|
|
9074
|
+
if (idx !== -1)
|
|
9075
|
+
bodyUses.uses.splice(idx, 1);
|
|
9076
|
+
}
|
|
9068
9077
|
resolveReassignedUses(defs.slice(methodDefStart), bodyUses.uses);
|
|
9069
9078
|
uses.push(...bodyUses.uses);
|
|
9070
9079
|
useIdCounter = bodyUses.nextId;
|
|
@@ -9403,6 +9412,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9403
9412
|
}
|
|
9404
9413
|
break;
|
|
9405
9414
|
}
|
|
9415
|
+
case "switch_expression":
|
|
9416
|
+
case "switch_statement": {
|
|
9417
|
+
const value = stmt.childForFieldName("condition") ?? stmt.childForFieldName("value");
|
|
9418
|
+
if (value) {
|
|
9419
|
+
const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
|
|
9420
|
+
uses.push(...valueUses.uses);
|
|
9421
|
+
useId = valueUses.nextId;
|
|
9422
|
+
}
|
|
9423
|
+
const body2 = stmt.childForFieldName("body");
|
|
9424
|
+
if (body2) {
|
|
9425
|
+
scopeStack.push(new Map);
|
|
9426
|
+
for (let i2 = 0;i2 < body2.childCount; i2++) {
|
|
9427
|
+
const group = body2.child(i2);
|
|
9428
|
+
if (!group)
|
|
9429
|
+
continue;
|
|
9430
|
+
const result = processBlock(group, defId, useId, scopeStack, isJavaScript);
|
|
9431
|
+
defs.push(...result.defs);
|
|
9432
|
+
uses.push(...result.uses);
|
|
9433
|
+
defId = result.nextDefId;
|
|
9434
|
+
useId = result.nextUseId;
|
|
9435
|
+
}
|
|
9436
|
+
scopeStack.pop();
|
|
9437
|
+
}
|
|
9438
|
+
break;
|
|
9439
|
+
}
|
|
9406
9440
|
case "block":
|
|
9407
9441
|
case "statement_block": {
|
|
9408
9442
|
scopeStack.push(new Map);
|
|
@@ -11404,6 +11438,7 @@ var DEFAULT_SOURCES = [
|
|
|
11404
11438
|
{ method: "get", class: "ConcurrentHashMap", type: "plugin_param", severity: "high", return_tainted: true },
|
|
11405
11439
|
{ method: "get", class: "Hashtable", type: "plugin_param", severity: "high", return_tainted: true },
|
|
11406
11440
|
{ method: "get", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
|
|
11441
|
+
{ method: "getProperty", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
|
|
11407
11442
|
{ method: "getText", class: "Message", type: "io_input", severity: "high", return_tainted: true },
|
|
11408
11443
|
{ method: "getPayload", type: "io_input", severity: "high", return_tainted: true },
|
|
11409
11444
|
{ method: "getData", type: "io_input", severity: "high", return_tainted: true },
|
|
@@ -11986,8 +12021,8 @@ var DEFAULT_SINKS = [
|
|
|
11986
12021
|
{ method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11987
12022
|
{ method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11988
12023
|
{ method: "print", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11989
|
-
{ method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
|
|
11990
|
-
{ method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
|
|
12024
|
+
{ method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
|
|
12025
|
+
{ method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
|
|
11991
12026
|
{ method: "write", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11992
12027
|
{ method: "print", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11993
12028
|
{ method: "println", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -20139,14 +20174,21 @@ class ConstantPropagator {
|
|
|
20139
20174
|
break;
|
|
20140
20175
|
}
|
|
20141
20176
|
}
|
|
20142
|
-
|
|
20177
|
+
if (!switchValue) {
|
|
20178
|
+
const valueNode = node.childForFieldName("value");
|
|
20179
|
+
if (valueNode) {
|
|
20180
|
+
const literal = this.literalValue(valueNode);
|
|
20181
|
+
switchValue = literal !== null ? { type: typeof literal === "number" ? "int" : "string", value: literal, sourceLine: getNodeLine(valueNode) } : this.evaluateExpression(valueNode);
|
|
20182
|
+
}
|
|
20183
|
+
}
|
|
20184
|
+
const switchBlock = node.children.find((c) => c.type === "switch_block" || c.type === "switch_body");
|
|
20143
20185
|
if (!switchBlock) {
|
|
20144
20186
|
for (const child of node.children) {
|
|
20145
20187
|
this.visit(child);
|
|
20146
20188
|
}
|
|
20147
20189
|
return;
|
|
20148
20190
|
}
|
|
20149
|
-
const caseGroups = switchBlock.children.filter((c) => c.type === "switch_block_statement_group" || c.type === "switch_rule");
|
|
20191
|
+
const caseGroups = switchBlock.children.filter((c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section");
|
|
20150
20192
|
if (switchValue && isKnown(switchValue)) {
|
|
20151
20193
|
let matchingIdx = -1;
|
|
20152
20194
|
let defaultIdx = -1;
|
|
@@ -20163,6 +20205,16 @@ class ConstantPropagator {
|
|
|
20163
20205
|
matchingIdx = i2;
|
|
20164
20206
|
}
|
|
20165
20207
|
}
|
|
20208
|
+
} else if (caseGroup.type === "switch_section") {
|
|
20209
|
+
if (child.type === "default") {
|
|
20210
|
+
defaultIdx = i2;
|
|
20211
|
+
} else if (child.type === "constant_pattern") {
|
|
20212
|
+
const inner = child.children.find((c) => c.isNamed);
|
|
20213
|
+
const caseValue = inner ? this.literalValue(inner) : null;
|
|
20214
|
+
if (caseValue !== null && caseValue === switchValue.value) {
|
|
20215
|
+
matchingIdx = i2;
|
|
20216
|
+
}
|
|
20217
|
+
}
|
|
20166
20218
|
}
|
|
20167
20219
|
}
|
|
20168
20220
|
}
|
|
@@ -20183,9 +20235,18 @@ class ConstantPropagator {
|
|
|
20183
20235
|
}
|
|
20184
20236
|
}
|
|
20185
20237
|
} else {
|
|
20238
|
+
const taintedBefore = new Set(this.tainted);
|
|
20239
|
+
const taintedAfter = new Set(taintedBefore);
|
|
20240
|
+
const wasInConditional = this.inConditionalBranch;
|
|
20241
|
+
this.inConditionalBranch = true;
|
|
20186
20242
|
for (const caseGroup of caseGroups) {
|
|
20243
|
+
this.tainted = new Set(taintedBefore);
|
|
20187
20244
|
this.visit(caseGroup);
|
|
20245
|
+
for (const v of this.tainted)
|
|
20246
|
+
taintedAfter.add(v);
|
|
20188
20247
|
}
|
|
20248
|
+
this.inConditionalBranch = wasInConditional;
|
|
20249
|
+
this.tainted = taintedAfter;
|
|
20189
20250
|
}
|
|
20190
20251
|
}
|
|
20191
20252
|
handleTernary(node) {
|
|
@@ -20236,6 +20297,17 @@ class ConstantPropagator {
|
|
|
20236
20297
|
}
|
|
20237
20298
|
return false;
|
|
20238
20299
|
}
|
|
20300
|
+
literalValue(node) {
|
|
20301
|
+
const text = getNodeText2(node, this.source);
|
|
20302
|
+
if (node.type === "integer_literal" || node.type === "decimal_integer_literal") {
|
|
20303
|
+
const n = Number(text.replace(/_/g, "").replace(/[uUlL]+$/, ""));
|
|
20304
|
+
return Number.isInteger(n) ? n : null;
|
|
20305
|
+
}
|
|
20306
|
+
if (node.type === "character_literal" || node.type === "string_literal") {
|
|
20307
|
+
return text.slice(1, -1);
|
|
20308
|
+
}
|
|
20309
|
+
return null;
|
|
20310
|
+
}
|
|
20239
20311
|
extractCaseValue(labelNode) {
|
|
20240
20312
|
for (const child of labelNode.children) {
|
|
20241
20313
|
if (child.type === "decimal_integer_literal") {
|
|
@@ -21260,7 +21332,11 @@ function annotationsInclude(annotations, targets) {
|
|
|
21260
21332
|
return false;
|
|
21261
21333
|
}
|
|
21262
21334
|
function simpleTypeName(ref) {
|
|
21263
|
-
|
|
21335
|
+
const name2 = ref.replace(/<.*$/, "").trim();
|
|
21336
|
+
return name2.substring(name2.lastIndexOf(".") + 1);
|
|
21337
|
+
}
|
|
21338
|
+
function isSupertypeLifecycleMethod(supertype, methodName) {
|
|
21339
|
+
return TIER_1_BY_SUPERTYPE.get(simpleTypeName(supertype))?.has(methodName) ?? false;
|
|
21264
21340
|
}
|
|
21265
21341
|
function looksLikeMainMethod(method) {
|
|
21266
21342
|
if (method.name !== "main")
|
|
@@ -21822,7 +21898,7 @@ function applyRequireEntryPath(fileAnalyses, options = {}) {
|
|
|
21822
21898
|
if (graph.methodsByKey.size === 0)
|
|
21823
21899
|
return;
|
|
21824
21900
|
const fileContext = buildFileContext(fileAnalyses);
|
|
21825
|
-
const entryPointKeys = collectEntryPointKeys(graph, fileContext);
|
|
21901
|
+
const entryPointKeys = collectEntryPointKeys(graph, fileContext, indexTypesByName(fileAnalyses));
|
|
21826
21902
|
const entryPointKeysByLanguage = countEntryPointKeysByLanguage(graph, entryPointKeys);
|
|
21827
21903
|
const profileResolver = makeProfileResolver(options.projectProfile);
|
|
21828
21904
|
for (const fa of fileAnalyses) {
|
|
@@ -21984,7 +22060,7 @@ function resolveCalleeKeys(call, methodsByKey, methodsByName) {
|
|
|
21984
22060
|
}
|
|
21985
22061
|
return candidates;
|
|
21986
22062
|
}
|
|
21987
|
-
function collectEntryPointKeys(graph, fileContext) {
|
|
22063
|
+
function collectEntryPointKeys(graph, fileContext, typesByName) {
|
|
21988
22064
|
const entryPoints = new Set;
|
|
21989
22065
|
for (const rec of graph.methodsByKey.values()) {
|
|
21990
22066
|
const fileCtx = fileContext.get(rec.file);
|
|
@@ -21995,11 +22071,48 @@ function collectEntryPointKeys(graph, fileContext) {
|
|
|
21995
22071
|
calls: fileCtx?.calls ?? null,
|
|
21996
22072
|
runtimeRegistrations: fileCtx?.runtimeRegistrations ?? null
|
|
21997
22073
|
});
|
|
21998
|
-
if (tier === "TIER_1_ENTRY_POINT")
|
|
22074
|
+
if (tier === "TIER_1_ENTRY_POINT" || inheritsLifecycleEntry(rec.method.name, rec.enclosingType, typesByName)) {
|
|
21999
22075
|
entryPoints.add(rec.key);
|
|
22076
|
+
}
|
|
22000
22077
|
}
|
|
22001
22078
|
return entryPoints;
|
|
22002
22079
|
}
|
|
22080
|
+
function indexTypesByName(fileAnalyses) {
|
|
22081
|
+
const byName = new Map;
|
|
22082
|
+
for (const fa of fileAnalyses) {
|
|
22083
|
+
for (const t of fa.analysis.types ?? []) {
|
|
22084
|
+
const list = byName.get(t.name) ?? [];
|
|
22085
|
+
list.push(t);
|
|
22086
|
+
byName.set(t.name, list);
|
|
22087
|
+
}
|
|
22088
|
+
}
|
|
22089
|
+
return byName;
|
|
22090
|
+
}
|
|
22091
|
+
var simpleName = (ref) => {
|
|
22092
|
+
const name2 = ref.replace(/<.*$/, "").trim();
|
|
22093
|
+
return name2.substring(name2.lastIndexOf(".") + 1);
|
|
22094
|
+
};
|
|
22095
|
+
function inheritsLifecycleEntry(methodName, enclosingType, typesByName) {
|
|
22096
|
+
if (!methodName || !enclosingType?.extends)
|
|
22097
|
+
return false;
|
|
22098
|
+
const seen = new Set([enclosingType]);
|
|
22099
|
+
let frontier = typesByName.get(simpleName(enclosingType.extends)) ?? [];
|
|
22100
|
+
for (let depth = 0;depth < 8 && frontier.length > 0; depth++) {
|
|
22101
|
+
const next = [];
|
|
22102
|
+
for (const ancestor of frontier) {
|
|
22103
|
+
if (seen.has(ancestor))
|
|
22104
|
+
continue;
|
|
22105
|
+
seen.add(ancestor);
|
|
22106
|
+
const supers = [ancestor.extends, ...ancestor.implements ?? []].filter((x) => !!x);
|
|
22107
|
+
if (supers.some((sup) => isSupertypeLifecycleMethod(sup, methodName)))
|
|
22108
|
+
return true;
|
|
22109
|
+
if (ancestor.extends)
|
|
22110
|
+
next.push(...typesByName.get(simpleName(ancestor.extends)) ?? []);
|
|
22111
|
+
}
|
|
22112
|
+
frontier = next;
|
|
22113
|
+
}
|
|
22114
|
+
return false;
|
|
22115
|
+
}
|
|
22003
22116
|
function buildFileContext(fileAnalyses) {
|
|
22004
22117
|
const out2 = new Map;
|
|
22005
22118
|
for (const fa of fileAnalyses) {
|
|
@@ -26307,7 +26420,8 @@ class LanguageSourcesPass {
|
|
|
26307
26420
|
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
26308
26421
|
ctx.addFinding(finding);
|
|
26309
26422
|
}
|
|
26310
|
-
|
|
26423
|
+
const xssSinkLines = new Set(ctx.getResult("taint-matcher").sinks.filter((s) => s.type === "xss").map((s) => s.line));
|
|
26424
|
+
for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file, xssSinkLines)) {
|
|
26311
26425
|
ctx.addFinding(finding);
|
|
26312
26426
|
}
|
|
26313
26427
|
for (const finding of findJavaMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
|
|
@@ -30861,7 +30975,7 @@ function findGoXssFindings(code, file) {
|
|
|
30861
30975
|
}
|
|
30862
30976
|
return out2;
|
|
30863
30977
|
}
|
|
30864
|
-
function findJavaResponseWriterXssFindings(code, file) {
|
|
30978
|
+
function findJavaResponseWriterXssFindings(code, file, taintSinkLines = new Set) {
|
|
30865
30979
|
const out2 = [];
|
|
30866
30980
|
const lines = code.split(`
|
|
30867
30981
|
`);
|
|
@@ -30897,6 +31011,8 @@ function findJavaResponseWriterXssFindings(code, file) {
|
|
|
30897
31011
|
const m = trimmed.match(chainRe);
|
|
30898
31012
|
if (!m)
|
|
30899
31013
|
continue;
|
|
31014
|
+
if (taintSinkLines.has(i2 + 1))
|
|
31015
|
+
continue;
|
|
30900
31016
|
const recv = m[1];
|
|
30901
31017
|
if (!respNames.has(recv))
|
|
30902
31018
|
continue;
|
|
@@ -33983,6 +34099,413 @@ class MyBatisAnnotationSqlSinkPass {
|
|
|
33983
34099
|
}
|
|
33984
34100
|
}
|
|
33985
34101
|
|
|
34102
|
+
// ../circle-ir/dist/analysis/passes/csharp-sanitizer-credit.js
|
|
34103
|
+
var CSHARP_ALLOWLIST_STRIP_RE = /\bRegex\s*\.\s*Replace\s*\([^,]*,\s*@?"\[\^(?:[A-Za-z0-9_\- ]|\\w|\\d|\\s)+\]"\s*,\s*@?"[A-Za-z0-9_]?"\s*\)/;
|
|
34104
|
+
var CSHARP_ALLOWLIST_FILTER_RE = /\bnew\s+string\s*\(\s*[A-Za-z_]\w*\s*\.\s*Where\s*\(\s*char\s*\.\s*Is(?:LetterOrDigit|Digit|Letter)\s*\)\s*\.\s*ToArray\s*\(\s*\)\s*\)/;
|
|
34105
|
+
var CSHARP_SANITIZER_RES = [
|
|
34106
|
+
{ re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: "xss" },
|
|
34107
|
+
{ re: /\bHtmlEncoder\s*\.\s*(?:Default\s*\.\s*)?Encode\s*\(/, type: "xss" },
|
|
34108
|
+
{ re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" },
|
|
34109
|
+
{ re: /\bSecurityElement\s*\.\s*Escape\s*\(/, type: "xpath_injection" },
|
|
34110
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "ldap_injection" },
|
|
34111
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "xpath_injection" },
|
|
34112
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "command_injection" },
|
|
34113
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "sql_injection" },
|
|
34114
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "crlf" },
|
|
34115
|
+
{ re: CSHARP_ALLOWLIST_FILTER_RE, type: "ldap_injection" },
|
|
34116
|
+
{ re: CSHARP_ALLOWLIST_FILTER_RE, type: "xpath_injection" },
|
|
34117
|
+
{ re: CSHARP_ALLOWLIST_FILTER_RE, type: "command_injection" },
|
|
34118
|
+
{ re: CSHARP_ALLOWLIST_FILTER_RE, type: "sql_injection" },
|
|
34119
|
+
{ re: CSHARP_ALLOWLIST_FILTER_RE, type: "crlf" }
|
|
34120
|
+
];
|
|
34121
|
+
var INERT_WHEN_ALPHANUMERIC = new Set([
|
|
34122
|
+
"ldap_injection",
|
|
34123
|
+
"xpath_injection",
|
|
34124
|
+
"command_injection",
|
|
34125
|
+
"sql_injection",
|
|
34126
|
+
"crlf",
|
|
34127
|
+
"xss",
|
|
34128
|
+
"path_traversal",
|
|
34129
|
+
"code_injection"
|
|
34130
|
+
]);
|
|
34131
|
+
var IDENT = "[A-Za-z_]\\w*";
|
|
34132
|
+
var ASSIGN_RE = new RegExp(`^\\s*(?:var\\s+|[A-Za-z_][\\w.<>\\[\\]?]*\\s+)?(${IDENT})\\s*=(?!=)\\s*(.+?);?\\s*$`);
|
|
34133
|
+
var ENDS_STATEMENT_RE = /[;{}]\s*(?:\/\/.*)?$/;
|
|
34134
|
+
function escapeRegex(s) {
|
|
34135
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34136
|
+
}
|
|
34137
|
+
function mentions(text, name2) {
|
|
34138
|
+
return new RegExp(`(?<![\\w])${escapeRegex(name2)}(?![\\w])`).test(text);
|
|
34139
|
+
}
|
|
34140
|
+
function insideParens(text, open) {
|
|
34141
|
+
let depth = 0;
|
|
34142
|
+
for (let i2 = open;i2 < text.length; i2++) {
|
|
34143
|
+
if (text[i2] === "(")
|
|
34144
|
+
depth++;
|
|
34145
|
+
else if (text[i2] === ")" && --depth === 0)
|
|
34146
|
+
return text.slice(open + 1, i2);
|
|
34147
|
+
}
|
|
34148
|
+
return null;
|
|
34149
|
+
}
|
|
34150
|
+
function leadingIf(text) {
|
|
34151
|
+
const m = /^\s*(?:\}\s*)?(?:else\s+)?if\s*\(/.exec(text);
|
|
34152
|
+
if (!m)
|
|
34153
|
+
return null;
|
|
34154
|
+
const open = m[0].length - 1;
|
|
34155
|
+
const cond = insideParens(text, open);
|
|
34156
|
+
if (cond === null)
|
|
34157
|
+
return null;
|
|
34158
|
+
return { cond: cond.trim(), rest: text.slice(open + cond.length + 2).trim() };
|
|
34159
|
+
}
|
|
34160
|
+
var EXITS_RE = /^(?:\{\s*)?(?:return\b|throw\b|continue\b|break\b)/;
|
|
34161
|
+
|
|
34162
|
+
class CSharpCredit {
|
|
34163
|
+
methods;
|
|
34164
|
+
lines;
|
|
34165
|
+
helperCache = new Map;
|
|
34166
|
+
guardHelperCache = new Map;
|
|
34167
|
+
constructor(code, methods) {
|
|
34168
|
+
this.methods = methods;
|
|
34169
|
+
this.lines = code.split(/\r?\n/);
|
|
34170
|
+
}
|
|
34171
|
+
methodAt(line) {
|
|
34172
|
+
let best = null;
|
|
34173
|
+
for (const m of this.methods) {
|
|
34174
|
+
if (line < m.start_line || line > m.end_line)
|
|
34175
|
+
continue;
|
|
34176
|
+
if (!best || m.end_line - m.start_line < best.end_line - best.start_line)
|
|
34177
|
+
best = m;
|
|
34178
|
+
}
|
|
34179
|
+
return best;
|
|
34180
|
+
}
|
|
34181
|
+
statements(from, to) {
|
|
34182
|
+
const out2 = [];
|
|
34183
|
+
for (let i2 = Math.max(from, 1);i2 < to; i2++) {
|
|
34184
|
+
let text = (this.lines[i2 - 1] ?? "").trimEnd();
|
|
34185
|
+
const first = i2;
|
|
34186
|
+
if (/^\s*(?:[\w.<>[\]?]+\s+)?[A-Za-z_]\w*\s*=(?!=)/.test(text) && !ENDS_STATEMENT_RE.test(text)) {
|
|
34187
|
+
while (i2 < to - 1 && i2 < first + 12) {
|
|
34188
|
+
i2++;
|
|
34189
|
+
text += " " + (this.lines[i2 - 1] ?? "").trim();
|
|
34190
|
+
if (ENDS_STATEMENT_RE.test(this.lines[i2 - 1] ?? ""))
|
|
34191
|
+
break;
|
|
34192
|
+
}
|
|
34193
|
+
}
|
|
34194
|
+
out2.push({ line: first, text });
|
|
34195
|
+
}
|
|
34196
|
+
return out2;
|
|
34197
|
+
}
|
|
34198
|
+
builtinSanitizes(type, text) {
|
|
34199
|
+
for (const { re, type: t } of CSHARP_SANITIZER_RES) {
|
|
34200
|
+
if (t === type && re.test(text))
|
|
34201
|
+
return true;
|
|
34202
|
+
}
|
|
34203
|
+
if (type === "ldap_injection") {
|
|
34204
|
+
if (/\.\s*Replace\s*\(\s*"\("\s*,/.test(text) && /\.\s*Replace\s*\(\s*"\)"\s*,/.test(text))
|
|
34205
|
+
return true;
|
|
34206
|
+
}
|
|
34207
|
+
if (type === "path_traversal") {
|
|
34208
|
+
if (/\b([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\([^)]*\)\s*\?\s*\1\s*:\s*null\b/.test(text))
|
|
34209
|
+
return true;
|
|
34210
|
+
}
|
|
34211
|
+
return false;
|
|
34212
|
+
}
|
|
34213
|
+
helpers(type) {
|
|
34214
|
+
const cached = this.helperCache.get(type);
|
|
34215
|
+
if (cached)
|
|
34216
|
+
return cached;
|
|
34217
|
+
const set = new Set;
|
|
34218
|
+
this.helperCache.set(type, set);
|
|
34219
|
+
for (const m of this.methods) {
|
|
34220
|
+
const returns = this.returnExprs(m);
|
|
34221
|
+
if (returns.length === 0)
|
|
34222
|
+
continue;
|
|
34223
|
+
const ok = returns.every((r) => {
|
|
34224
|
+
if (this.builtinSanitizes(type, r.expr))
|
|
34225
|
+
return true;
|
|
34226
|
+
const v = /^([A-Za-z_]\w*)$/.exec(r.expr.trim());
|
|
34227
|
+
return !!v && this.sanitizedVarsIn(type, m.start_line, r.line, false).has(v[1]);
|
|
34228
|
+
});
|
|
34229
|
+
if (ok)
|
|
34230
|
+
set.add(m.name);
|
|
34231
|
+
}
|
|
34232
|
+
return set;
|
|
34233
|
+
}
|
|
34234
|
+
returnExprs(m) {
|
|
34235
|
+
const out2 = [];
|
|
34236
|
+
for (const st of this.statements(m.start_line, m.end_line + 1)) {
|
|
34237
|
+
for (const r of st.text.matchAll(/\breturn\s+([^;]+);/g))
|
|
34238
|
+
out2.push({ line: st.line, expr: r[1] });
|
|
34239
|
+
if (st.line === m.start_line) {
|
|
34240
|
+
const body2 = /\)\s*=>\s*(.+?);\s*$/.exec(st.text);
|
|
34241
|
+
if (body2)
|
|
34242
|
+
out2.push({ line: st.line, expr: body2[1] });
|
|
34243
|
+
}
|
|
34244
|
+
}
|
|
34245
|
+
return out2;
|
|
34246
|
+
}
|
|
34247
|
+
sanitizes(type, text) {
|
|
34248
|
+
if (this.builtinSanitizes(type, text))
|
|
34249
|
+
return true;
|
|
34250
|
+
for (const name2 of this.helpers(type)) {
|
|
34251
|
+
if (new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`).test(text))
|
|
34252
|
+
return true;
|
|
34253
|
+
}
|
|
34254
|
+
return false;
|
|
34255
|
+
}
|
|
34256
|
+
sanitizedVarsIn(type, from, to, withHelpers) {
|
|
34257
|
+
const sanitized = new Set;
|
|
34258
|
+
for (const st of this.statements(from, to)) {
|
|
34259
|
+
const m = ASSIGN_RE.exec(st.text);
|
|
34260
|
+
if (!m)
|
|
34261
|
+
continue;
|
|
34262
|
+
const [, lhs, rhs] = m;
|
|
34263
|
+
const credited = (withHelpers ? this.sanitizes(type, rhs) : this.builtinSanitizes(type, rhs)) || [...sanitized].some((v) => mentions(rhs, v));
|
|
34264
|
+
if (credited)
|
|
34265
|
+
sanitized.add(lhs);
|
|
34266
|
+
else
|
|
34267
|
+
sanitized.delete(lhs);
|
|
34268
|
+
}
|
|
34269
|
+
return sanitized;
|
|
34270
|
+
}
|
|
34271
|
+
sanitizedVarsAt(type, sinkLine) {
|
|
34272
|
+
return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
|
|
34273
|
+
}
|
|
34274
|
+
sinkText(line) {
|
|
34275
|
+
const text = this.lines[line - 1] ?? "";
|
|
34276
|
+
const guarded = leadingIf(text);
|
|
34277
|
+
return guarded && guarded.rest !== "" ? guarded.rest : text;
|
|
34278
|
+
}
|
|
34279
|
+
allowlistVars(cond) {
|
|
34280
|
+
const out2 = [];
|
|
34281
|
+
const anchored = new RegExp(`\\bRegex\\s*\\.\\s*IsMatch\\s*\\(\\s*(${IDENT})\\s*,\\s*@?"\\^\\[(?:[A-Za-z0-9_\\- ]|\\\\w|\\\\d)+\\](?:[+*]|\\{\\d+(?:,\\d*)?\\})\\$"`, "g");
|
|
34282
|
+
for (const m of cond.matchAll(anchored))
|
|
34283
|
+
out2.push(m[1]);
|
|
34284
|
+
const methodGroup = new RegExp(`(${IDENT})\\s*\\.\\s*All\\s*\\(\\s*char\\s*\\.\\s*Is(?:LetterOrDigit|Digit|Letter)\\s*\\)`, "g");
|
|
34285
|
+
for (const m of cond.matchAll(methodGroup))
|
|
34286
|
+
out2.push(m[1]);
|
|
34287
|
+
const lambda = new RegExp(`(${IDENT})\\s*\\.\\s*All\\s*\\(\\s*(${IDENT})\\s*=>`, "g");
|
|
34288
|
+
for (const m of cond.matchAll(lambda)) {
|
|
34289
|
+
const open = cond.indexOf("(", m.index + m[1].length);
|
|
34290
|
+
const body2 = insideParens(cond, open)?.replace(new RegExp(`^\\s*${m[2]}\\s*=>`), "") ?? "";
|
|
34291
|
+
const c = escapeRegex(m[2]);
|
|
34292
|
+
const residue = body2.replace(new RegExp(`char\\s*\\.\\s*Is(?:LetterOrDigit|Digit|Letter)\\s*\\(\\s*${c}\\s*\\)`, "g"), "").replace(new RegExp(`${c}\\s*==\\s*'[_\\-]'`, "g"), "").replace(/\|\||\s/g, "");
|
|
34293
|
+
if (residue === "")
|
|
34294
|
+
out2.push(m[1]);
|
|
34295
|
+
}
|
|
34296
|
+
return out2;
|
|
34297
|
+
}
|
|
34298
|
+
numericVars(cond, parsedFrom) {
|
|
34299
|
+
const out2 = [];
|
|
34300
|
+
const tryParse = new RegExp(`\\b(?:int|long|short|byte|uint|ulong|decimal|double|float|Int32|Int64|Guid)\\s*\\.\\s*TryParse\\s*\\(\\s*(${IDENT})\\s*,`, "g");
|
|
34301
|
+
for (const m of cond.matchAll(tryParse))
|
|
34302
|
+
out2.push(m[1]);
|
|
34303
|
+
for (const [parsed, from] of parsedFrom) {
|
|
34304
|
+
if (new RegExp(`(?<![\\w])${escapeRegex(parsed)}\\s*(?:!=\\s*null|\\.\\s*HasValue)\\b`).test(cond))
|
|
34305
|
+
out2.push(from);
|
|
34306
|
+
}
|
|
34307
|
+
return out2;
|
|
34308
|
+
}
|
|
34309
|
+
hostVars(cond, hostOf) {
|
|
34310
|
+
const out2 = [];
|
|
34311
|
+
const compares = (expr) => {
|
|
34312
|
+
const e = escapeRegex(expr).replace(/\\ /g, "\\s*");
|
|
34313
|
+
return new RegExp(`${e}\\s*==\\s*@?"[^"]+"`).test(cond) || new RegExp(`@?"[^"]+"\\s*==\\s*${e}`).test(cond) || new RegExp(`${e}\\s*\\.\\s*Equals\\s*\\(`).test(cond) || new RegExp(`\\.\\s*Contains\\s*\\(\\s*${e}\\s*[),]`).test(cond) || new RegExp(`\\bIndexOf\\s*\\([^()]*,\\s*${e}\\s*\\)\\s*>=\\s*0`).test(cond);
|
|
34314
|
+
};
|
|
34315
|
+
const direct = new RegExp(`new\\s+(?:System\\s*\\.\\s*)?Uri\\s*\\(\\s*(${IDENT})\\s*\\)\\s*\\.\\s*Host\\b`, "g");
|
|
34316
|
+
for (const m of cond.matchAll(direct)) {
|
|
34317
|
+
if (compares(m[0]))
|
|
34318
|
+
out2.push(m[1]);
|
|
34319
|
+
}
|
|
34320
|
+
for (const [hostVar, from] of hostOf) {
|
|
34321
|
+
if (mentions(cond, hostVar) && compares(hostVar))
|
|
34322
|
+
out2.push(from);
|
|
34323
|
+
}
|
|
34324
|
+
return out2;
|
|
34325
|
+
}
|
|
34326
|
+
guardHelpers(kind) {
|
|
34327
|
+
const cached = this.guardHelperCache.get(kind);
|
|
34328
|
+
if (cached)
|
|
34329
|
+
return cached;
|
|
34330
|
+
const map = new Map;
|
|
34331
|
+
this.guardHelperCache.set(kind, map);
|
|
34332
|
+
for (const m of this.methods) {
|
|
34333
|
+
const signature = this.lines[m.start_line - 1] ?? "";
|
|
34334
|
+
const open = signature.indexOf("(", signature.indexOf(m.name));
|
|
34335
|
+
const params = open === -1 ? null : insideParens(signature, open);
|
|
34336
|
+
if (!params)
|
|
34337
|
+
continue;
|
|
34338
|
+
const names = params.split(",").map((p) => p.trim().split(/\s+/).pop() ?? "");
|
|
34339
|
+
const returns = this.returnExprs(m);
|
|
34340
|
+
if (returns.length !== 1)
|
|
34341
|
+
continue;
|
|
34342
|
+
const proven = this.provenBy(kind, returns[0].expr, new Map, new Map, false);
|
|
34343
|
+
const pos = names.findIndex((n) => proven.includes(n));
|
|
34344
|
+
if (pos !== -1)
|
|
34345
|
+
map.set(m.name, pos);
|
|
34346
|
+
}
|
|
34347
|
+
return map;
|
|
34348
|
+
}
|
|
34349
|
+
provenBy(kind, cond, parsedFrom, hostOf, withHelpers) {
|
|
34350
|
+
const out2 = kind === "allowlist" ? this.allowlistVars(cond) : kind === "numeric" ? this.numericVars(cond, parsedFrom) : this.hostVars(cond, hostOf);
|
|
34351
|
+
if (withHelpers) {
|
|
34352
|
+
for (const [name2, pos] of this.guardHelpers(kind)) {
|
|
34353
|
+
for (const call of cond.matchAll(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`, "g"))) {
|
|
34354
|
+
const args2 = insideParens(cond, call.index + call[0].length - 1)?.split(",") ?? [];
|
|
34355
|
+
const arg = args2[pos]?.trim();
|
|
34356
|
+
if (arg && new RegExp(`^${IDENT}$`).test(arg))
|
|
34357
|
+
out2.push(arg);
|
|
34358
|
+
}
|
|
34359
|
+
}
|
|
34360
|
+
}
|
|
34361
|
+
return out2;
|
|
34362
|
+
}
|
|
34363
|
+
guardedVarsAt(type, sinkLine) {
|
|
34364
|
+
const kinds = [];
|
|
34365
|
+
if (INERT_WHEN_ALPHANUMERIC.has(type))
|
|
34366
|
+
kinds.push("allowlist", "numeric");
|
|
34367
|
+
if (type === "ssrf")
|
|
34368
|
+
kinds.push("host");
|
|
34369
|
+
const proven = new Set;
|
|
34370
|
+
if (kinds.length === 0)
|
|
34371
|
+
return proven;
|
|
34372
|
+
const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
|
|
34373
|
+
const before = this.statements(scopeStart, sinkLine);
|
|
34374
|
+
const boolOf = new Map;
|
|
34375
|
+
const parsedFrom = new Map;
|
|
34376
|
+
const hostOf = new Map;
|
|
34377
|
+
const parseAnywhere = new RegExp(`(${IDENT})\\s*=\\s*(?:\\([^)]*\\)\\s*)?(?:int|long|short|byte|uint|ulong|decimal|double|float|Int32|Int64)\\s*\\.\\s*Parse\\s*\\(\\s*(${IDENT})\\s*[,)]`, "g");
|
|
34378
|
+
for (const st of before) {
|
|
34379
|
+
for (const p of st.text.matchAll(parseAnywhere))
|
|
34380
|
+
parsedFrom.set(p[1], p[2]);
|
|
34381
|
+
const m = ASSIGN_RE.exec(st.text);
|
|
34382
|
+
if (!m)
|
|
34383
|
+
continue;
|
|
34384
|
+
const [, lhs, rhs] = m;
|
|
34385
|
+
const parse2 = new RegExp(`^(?:\\([^)]*\\)\\s*)?(?:int|long|short|byte|uint|ulong|decimal|double|float|Int32|Int64)\\s*\\.\\s*Parse\\s*\\(\\s*(${IDENT})\\s*[,)]`).exec(rhs);
|
|
34386
|
+
if (parse2)
|
|
34387
|
+
parsedFrom.set(lhs, parse2[1]);
|
|
34388
|
+
const host = new RegExp(`^new\\s+(?:System\\s*\\.\\s*)?Uri\\s*\\(\\s*(${IDENT})\\s*\\)\\s*\\.\\s*Host$`).exec(rhs.trim());
|
|
34389
|
+
if (host)
|
|
34390
|
+
hostOf.set(lhs, host[1]);
|
|
34391
|
+
boolOf.set(lhs, rhs);
|
|
34392
|
+
}
|
|
34393
|
+
const credit = (cond) => {
|
|
34394
|
+
const resolved = new RegExp(`^${IDENT}$`).test(cond) ? boolOf.get(cond) ?? cond : cond;
|
|
34395
|
+
for (const kind of kinds) {
|
|
34396
|
+
for (const v of this.provenBy(kind, resolved, parsedFrom, hostOf, true))
|
|
34397
|
+
proven.add(v);
|
|
34398
|
+
}
|
|
34399
|
+
};
|
|
34400
|
+
const own = leadingIf(this.lines[sinkLine - 1] ?? "");
|
|
34401
|
+
if (own && !own.cond.startsWith("!"))
|
|
34402
|
+
credit(own.cond);
|
|
34403
|
+
let depth = 0;
|
|
34404
|
+
for (let i2 = sinkLine - 1;i2 >= scopeStart; i2--) {
|
|
34405
|
+
const text = this.lines[i2 - 1] ?? "";
|
|
34406
|
+
for (let c = text.length - 1;c >= 0; c--) {
|
|
34407
|
+
if (text[c] === "}")
|
|
34408
|
+
depth++;
|
|
34409
|
+
else if (text[c] === "{") {
|
|
34410
|
+
if (depth > 0) {
|
|
34411
|
+
depth--;
|
|
34412
|
+
continue;
|
|
34413
|
+
}
|
|
34414
|
+
let header = text.slice(0, c);
|
|
34415
|
+
for (let h = i2 - 1;header.trim() === "" && h >= scopeStart; h--)
|
|
34416
|
+
header = this.lines[h - 1] ?? "";
|
|
34417
|
+
const enclosing = leadingIf(header);
|
|
34418
|
+
if (enclosing && !enclosing.cond.startsWith("!"))
|
|
34419
|
+
credit(enclosing.cond);
|
|
34420
|
+
}
|
|
34421
|
+
}
|
|
34422
|
+
}
|
|
34423
|
+
for (let i2 = scopeStart;i2 < sinkLine; i2++) {
|
|
34424
|
+
const guard = leadingIf(this.lines[i2 - 1] ?? "");
|
|
34425
|
+
if (!guard)
|
|
34426
|
+
continue;
|
|
34427
|
+
let rest = guard.rest;
|
|
34428
|
+
for (let n = i2 + 1;rest === "" && n < sinkLine; n++)
|
|
34429
|
+
rest = (this.lines[n - 1] ?? "").trim();
|
|
34430
|
+
if (!EXITS_RE.test(rest))
|
|
34431
|
+
continue;
|
|
34432
|
+
if (guard.cond.startsWith("!")) {
|
|
34433
|
+
const inner = guard.cond.slice(1).trim();
|
|
34434
|
+
const wrapped = inner.startsWith("(") && insideParens(inner, 0)?.length === inner.length - 2;
|
|
34435
|
+
credit(wrapped ? inner.slice(1, -1) : inner);
|
|
34436
|
+
} else if (/!=/.test(guard.cond) && !/[|&]/.test(guard.cond))
|
|
34437
|
+
credit(guard.cond.replace("!=", "=="));
|
|
34438
|
+
}
|
|
34439
|
+
return proven;
|
|
34440
|
+
}
|
|
34441
|
+
safeBuilders(credited, sinkLine) {
|
|
34442
|
+
const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
|
|
34443
|
+
const onlySafe = (expr) => {
|
|
34444
|
+
const idents = expr.replace(/\$?@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ").replace(/\.\s*[A-Za-z_]\w*\s*\(\s*\)/g, " ").match(/[A-Za-z_]\w*/g) ?? [];
|
|
34445
|
+
return idents.every((id) => credited.has(id));
|
|
34446
|
+
};
|
|
34447
|
+
const state = new Map;
|
|
34448
|
+
const declRe = new RegExp(`\\b(?:StringBuilder|var)\\s+(${IDENT})\\s*=\\s*new\\s+(?:System\\s*\\.\\s*Text\\s*\\.\\s*)?StringBuilder\\s*\\(`);
|
|
34449
|
+
const writeRe = new RegExp(`^\\s*(${IDENT})\\s*\\.\\s*(?:Append|AppendLine|AppendFormat|Insert)\\s*\\(`);
|
|
34450
|
+
for (const st of this.statements(scopeStart, sinkLine)) {
|
|
34451
|
+
const decl = declRe.exec(st.text);
|
|
34452
|
+
if (decl) {
|
|
34453
|
+
const args2 = insideParens(st.text, decl.index + decl[0].length - 1) ?? "";
|
|
34454
|
+
state.set(decl[1], onlySafe(args2));
|
|
34455
|
+
continue;
|
|
34456
|
+
}
|
|
34457
|
+
const write = writeRe.exec(st.text);
|
|
34458
|
+
if (write && state.has(write[1])) {
|
|
34459
|
+
const args2 = insideParens(st.text, write[0].length - 1);
|
|
34460
|
+
if (args2 === null || !onlySafe(args2))
|
|
34461
|
+
state.set(write[1], false);
|
|
34462
|
+
}
|
|
34463
|
+
}
|
|
34464
|
+
return new Set([...state].filter(([, safe]) => safe).map(([name2]) => name2));
|
|
34465
|
+
}
|
|
34466
|
+
creditedOnLine(line) {
|
|
34467
|
+
const text = this.sinkText(line);
|
|
34468
|
+
const types = new Set(CSHARP_SANITIZER_RES.map((r) => r.type));
|
|
34469
|
+
for (const t of INERT_WHEN_ALPHANUMERIC)
|
|
34470
|
+
types.add(t);
|
|
34471
|
+
types.add("ssrf");
|
|
34472
|
+
for (const type of types) {
|
|
34473
|
+
if (this.sanitizes(type, text))
|
|
34474
|
+
return true;
|
|
34475
|
+
const credited = new Set([...this.sanitizedVarsAt(type, line), ...this.guardedVarsAt(type, line)]);
|
|
34476
|
+
for (const v of this.safeBuilders(credited, line))
|
|
34477
|
+
credited.add(v);
|
|
34478
|
+
for (const v of credited)
|
|
34479
|
+
if (mentions(text, v))
|
|
34480
|
+
return true;
|
|
34481
|
+
}
|
|
34482
|
+
return this.fixedHostRequest(line);
|
|
34483
|
+
}
|
|
34484
|
+
fixedHostRequest(sinkLine) {
|
|
34485
|
+
const fixedLiteral = '\\$?@?"https?:\\/\\/[A-Za-z0-9.\\-]+(?::\\d+)?[\\/?#][^"]*"';
|
|
34486
|
+
const text = this.lines[sinkLine - 1] ?? "";
|
|
34487
|
+
const argStart = `\\(\\s*(?:new\\s+(?:System\\s*\\.\\s*)?Uri\\s*\\(\\s*)?`;
|
|
34488
|
+
if (new RegExp(argStart + fixedLiteral).test(text))
|
|
34489
|
+
return true;
|
|
34490
|
+
const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
|
|
34491
|
+
const fixed = new Set;
|
|
34492
|
+
for (const st of this.statements(scopeStart, sinkLine)) {
|
|
34493
|
+
const m = ASSIGN_RE.exec(st.text);
|
|
34494
|
+
if (!m)
|
|
34495
|
+
continue;
|
|
34496
|
+
if (new RegExp("^(?:new\\s+(?:System\\s*\\.\\s*)?Uri\\s*\\(\\s*)?" + fixedLiteral).test(m[2].trim()))
|
|
34497
|
+
fixed.add(m[1]);
|
|
34498
|
+
else
|
|
34499
|
+
fixed.delete(m[1]);
|
|
34500
|
+
}
|
|
34501
|
+
for (const v of fixed) {
|
|
34502
|
+
if (new RegExp(argStart + escapeRegex(v) + "\\s*[,)]").test(text))
|
|
34503
|
+
return true;
|
|
34504
|
+
}
|
|
34505
|
+
return false;
|
|
34506
|
+
}
|
|
34507
|
+
}
|
|
34508
|
+
|
|
33986
34509
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
33987
34510
|
function escapeReSf(s) {
|
|
33988
34511
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -34285,7 +34808,6 @@ function jsSsrfHostGuardedLines(code) {
|
|
|
34285
34808
|
}
|
|
34286
34809
|
return covered;
|
|
34287
34810
|
}
|
|
34288
|
-
var CSHARP_ALLOWLIST_STRIP_RE = /\bRegex\s*\.\s*Replace\s*\([^,]*,\s*@?"\[\^(?:[A-Za-z0-9_\- ]|\\w|\\d|\\s)+\]"\s*,\s*@?"[A-Za-z0-9_]?"\s*\)/;
|
|
34289
34811
|
function isUnwrittenLocalMapKeyRead(code, source) {
|
|
34290
34812
|
if (source.type !== "plugin_param")
|
|
34291
34813
|
return false;
|
|
@@ -34312,15 +34834,6 @@ function isUnwrittenLocalMapKeyRead(code, source) {
|
|
|
34312
34834
|
return false;
|
|
34313
34835
|
return true;
|
|
34314
34836
|
}
|
|
34315
|
-
var CSHARP_SANITIZER_RES = [
|
|
34316
|
-
{ re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: "xss" },
|
|
34317
|
-
{ re: /\bHtmlEncoder\s*\.\s*Encode\s*\(/, type: "xss" },
|
|
34318
|
-
{ re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" },
|
|
34319
|
-
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "ldap_injection" },
|
|
34320
|
-
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "xpath_injection" },
|
|
34321
|
-
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "command_injection" },
|
|
34322
|
-
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "sql_injection" }
|
|
34323
|
-
];
|
|
34324
34837
|
function csharpSanitizedVarsByType(code) {
|
|
34325
34838
|
const lines = code.split(`
|
|
34326
34839
|
`);
|
|
@@ -34353,7 +34866,7 @@ function csharpSanitizedVarsByType(code) {
|
|
|
34353
34866
|
for (const [type, set] of byType) {
|
|
34354
34867
|
if (set.has(lhs))
|
|
34355
34868
|
continue;
|
|
34356
|
-
const refsSanitized = [...set].some((v) => new RegExp(`(?<![\\w])${
|
|
34869
|
+
const refsSanitized = [...set].some((v) => new RegExp(`(?<![\\w])${escapeRegex2(v)}(?![\\w])`).test(rhs));
|
|
34357
34870
|
if (refsSanitized) {
|
|
34358
34871
|
set.add(lhs);
|
|
34359
34872
|
changed = true;
|
|
@@ -35249,22 +35762,34 @@ class SinkFilterPass {
|
|
|
35249
35762
|
const sourceLines = ctx.code.split(`
|
|
35250
35763
|
`);
|
|
35251
35764
|
const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
|
|
35765
|
+
const csFields = new Set(ctx.graph.ir.types.flatMap((t) => (t.fields ?? []).map((f) => f.name)));
|
|
35766
|
+
const credit = new CSharpCredit(ctx.code, ctx.graph.ir.types.flatMap((t) => t.methods));
|
|
35252
35767
|
const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
|
|
35253
35768
|
filtered = filtered.filter((sink) => {
|
|
35254
35769
|
if (sink.type === "xxe" && xxeHardened)
|
|
35255
35770
|
return false;
|
|
35256
35771
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
35257
|
-
|
|
35258
|
-
|
|
35772
|
+
if (credit.sanitizes(sink.type, sinkLineText))
|
|
35773
|
+
return false;
|
|
35774
|
+
if (sink.type === "ssrf" && credit.fixedHostRequest(sink.line))
|
|
35775
|
+
return false;
|
|
35776
|
+
const scoped = credit.sanitizedVarsAt(sink.type, sink.line);
|
|
35777
|
+
const fileWide = sanitizedByType.get(sink.type);
|
|
35778
|
+
const sanitizedVars = new Set(scoped);
|
|
35779
|
+
if (fileWide) {
|
|
35780
|
+
for (const v of fileWide)
|
|
35781
|
+
if (csFields.has(v))
|
|
35782
|
+
sanitizedVars.add(v);
|
|
35783
|
+
}
|
|
35784
|
+
for (const v of credit.guardedVarsAt(sink.type, sink.line))
|
|
35785
|
+
sanitizedVars.add(v);
|
|
35786
|
+
for (const v of credit.safeBuilders(sanitizedVars, sink.line))
|
|
35787
|
+
sanitizedVars.add(v);
|
|
35788
|
+
const received = credit.sinkText(sink.line);
|
|
35789
|
+
for (const v of sanitizedVars) {
|
|
35790
|
+
if (new RegExp(`(?<![\\w])${escapeRegex2(v)}(?![\\w])`).test(received))
|
|
35259
35791
|
return false;
|
|
35260
35792
|
}
|
|
35261
|
-
const sanitizedVars = sanitizedByType.get(sink.type);
|
|
35262
|
-
if (sanitizedVars && sanitizedVars.size > 0) {
|
|
35263
|
-
for (const v of sanitizedVars) {
|
|
35264
|
-
if (new RegExp(`(?<![\\w])${escapeRegex(v)}(?![\\w])`).test(sinkLineText))
|
|
35265
|
-
return false;
|
|
35266
|
-
}
|
|
35267
|
-
}
|
|
35268
35793
|
return true;
|
|
35269
35794
|
});
|
|
35270
35795
|
}
|
|
@@ -35272,7 +35797,7 @@ class SinkFilterPass {
|
|
|
35272
35797
|
const sourceLines = ctx.code.split(`
|
|
35273
35798
|
`);
|
|
35274
35799
|
const constBaseClients = new Set;
|
|
35275
|
-
const declRe = /\b(?:var|HttpClient)\s+([A-Za-z_]\w*)\s*=\s*new\s+HttpClient\s*\{[^}]*\bBaseAddress\s*=\s*new\s+Uri\s*\(\s*(?:@?"[^"]*"|\$?"[^"{}]*")\s*\)/;
|
|
35800
|
+
const declRe = /\b(?:var|HttpClient)\s+([A-Za-z_]\w*)\s*=\s*new\s+HttpClient\s*(?:\([^)]*\)\s*)?\{[^}]*\bBaseAddress\s*=\s*new\s+(?:System\s*\.\s*)?Uri\s*\(\s*(?:@?"[^"]*"|\$?"[^"{}]*")\s*\)/;
|
|
35276
35801
|
for (const line of sourceLines) {
|
|
35277
35802
|
const m = declRe.exec(line);
|
|
35278
35803
|
if (m)
|
|
@@ -35286,14 +35811,22 @@ class SinkFilterPass {
|
|
|
35286
35811
|
const text = sourceLines[sink.line - 1] ?? "";
|
|
35287
35812
|
let onConstClient = false;
|
|
35288
35813
|
for (const v of constBaseClients) {
|
|
35289
|
-
if (new RegExp(`(?<![\\w])${
|
|
35814
|
+
if (new RegExp(`(?<![\\w])${escapeRegex2(v)}\\s*\\.`).test(text)) {
|
|
35290
35815
|
onConstClient = true;
|
|
35291
35816
|
break;
|
|
35292
35817
|
}
|
|
35293
35818
|
}
|
|
35294
35819
|
if (!onConstClient)
|
|
35295
35820
|
return true;
|
|
35296
|
-
|
|
35821
|
+
let m = relativeLiteralArgRe.exec(text);
|
|
35822
|
+
if (!m) {
|
|
35823
|
+
const arg = /Async\s*\(\s*([A-Za-z_]\w*)\s*[,)]/.exec(text)?.[1];
|
|
35824
|
+
if (arg) {
|
|
35825
|
+
const assigned = new RegExp(`\\b${escapeRegex2(arg)}\\s*=\\s*(?:@?\\$?)"([^"]*)"`);
|
|
35826
|
+
for (let i2 = sink.line - 2;i2 >= 0 && !m; i2--)
|
|
35827
|
+
m = assigned.exec(sourceLines[i2] ?? "");
|
|
35828
|
+
}
|
|
35829
|
+
}
|
|
35297
35830
|
if (!m)
|
|
35298
35831
|
return true;
|
|
35299
35832
|
const lit = m[1];
|
|
@@ -35329,7 +35862,7 @@ class SinkFilterPass {
|
|
|
35329
35862
|
const wrappers = findPythonLdapStripWrappers(sourceLines);
|
|
35330
35863
|
const allLdapSanitizers = [...PY_BUILTIN_LDAP_SANITIZERS, ...wrappers];
|
|
35331
35864
|
if (allLdapSanitizers.length > 0) {
|
|
35332
|
-
const ldapPatterns = allLdapSanitizers.map((name2) => new RegExp(`\\b${
|
|
35865
|
+
const ldapPatterns = allLdapSanitizers.map((name2) => new RegExp(`\\b${escapeRegex2(name2)}\\s*\\(`));
|
|
35333
35866
|
filtered = filtered.filter((sink) => {
|
|
35334
35867
|
if (sink.type !== "ldap_injection")
|
|
35335
35868
|
return true;
|
|
@@ -35415,7 +35948,7 @@ class SinkFilterPass {
|
|
|
35415
35948
|
return { sources, sinks: filtered, sanitizers };
|
|
35416
35949
|
}
|
|
35417
35950
|
}
|
|
35418
|
-
function
|
|
35951
|
+
function escapeRegex2(s) {
|
|
35419
35952
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35420
35953
|
}
|
|
35421
35954
|
var RESERVED_KEYWORDS = new Set([
|
|
@@ -35497,7 +36030,7 @@ function extractSourceIdentifiers(text) {
|
|
|
35497
36030
|
return out2;
|
|
35498
36031
|
}
|
|
35499
36032
|
function isAssignedFromSanitizerPattern(sourceLines, sinkLine, varName, sanitizerPatterns, lookback = 30) {
|
|
35500
|
-
const escapedVar =
|
|
36033
|
+
const escapedVar = escapeRegex2(varName);
|
|
35501
36034
|
const assignRe = new RegExp(`(?:\\b(?:const|let|var|final|String)\\s+)?\\b${escapedVar}\\s*=\\s*(.+)`);
|
|
35502
36035
|
const fnBoundaryRe = /^\s*(?:def\s+\w+\s*\(|function\s+\w+\s*\(|\w+\s*[:=]\s*(?:async\s+)?\(?[^)]*\)?\s*=>)/;
|
|
35503
36036
|
const lo = Math.max(0, sinkLine - 1 - lookback);
|
|
@@ -37066,7 +37599,7 @@ class LibraryProfileXssGatePass {
|
|
|
37066
37599
|
|
|
37067
37600
|
// ../circle-ir/dist/analysis/passes/taint-propagation-pass.js
|
|
37068
37601
|
var CONTAINER_READ_SOURCES = new Set(["plugin_param", "config_param"]);
|
|
37069
|
-
function makeConstPropVeto(constProp, graph, sources, code) {
|
|
37602
|
+
function makeConstPropVeto(constProp, graph, sources, code, language) {
|
|
37070
37603
|
const lines = typeof code === "string" ? code.split(`
|
|
37071
37604
|
`) : [];
|
|
37072
37605
|
const selfReassign = new Map;
|
|
@@ -37103,6 +37636,8 @@ function makeConstPropVeto(constProp, graph, sources, code) {
|
|
|
37103
37636
|
return false;
|
|
37104
37637
|
if (r.reason !== "variable_not_tainted")
|
|
37105
37638
|
return true;
|
|
37639
|
+
if (language === "csharp")
|
|
37640
|
+
return false;
|
|
37106
37641
|
const lo = Math.min(sourceLine, sinkLine);
|
|
37107
37642
|
let d = defAt(step.variable, step.line);
|
|
37108
37643
|
let selfHops = 0;
|
|
@@ -37141,7 +37676,7 @@ class TaintPropagationPass {
|
|
|
37141
37676
|
return { flows: [] };
|
|
37142
37677
|
}
|
|
37143
37678
|
const propagationResult = propagateTaint(graph, sources, sinks, sanitizers);
|
|
37144
|
-
const isVetoed = makeConstPropVeto(constProp, graph, sources, ctx.code);
|
|
37679
|
+
const isVetoed = makeConstPropVeto(constProp, graph, sources, ctx.code, ctx.language);
|
|
37145
37680
|
const verifiedFlows = propagationResult.flows.filter((flow) => {
|
|
37146
37681
|
if (constProp.unreachableLines.has(flow.sink.line))
|
|
37147
37682
|
return false;
|
|
@@ -37711,7 +38246,7 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
37711
38246
|
if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
37712
38247
|
continue;
|
|
37713
38248
|
}
|
|
37714
|
-
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
|
|
38249
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line, unreachableLines)) {
|
|
37715
38250
|
continue;
|
|
37716
38251
|
}
|
|
37717
38252
|
flows.push({
|
|
@@ -37751,7 +38286,7 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
37751
38286
|
if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
37752
38287
|
continue;
|
|
37753
38288
|
}
|
|
37754
|
-
if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
|
|
38289
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line, unreachableLines)) {
|
|
37755
38290
|
continue;
|
|
37756
38291
|
}
|
|
37757
38292
|
flows.push({
|
|
@@ -37870,7 +38405,7 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
37870
38405
|
if (paramSource) {
|
|
37871
38406
|
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
|
|
37872
38407
|
if (!exists) {
|
|
37873
|
-
if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
|
|
38408
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line, unreachableLines)) {
|
|
37874
38409
|
continue;
|
|
37875
38410
|
}
|
|
37876
38411
|
flows.push({
|
|
@@ -37893,6 +38428,96 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
37893
38428
|
}
|
|
37894
38429
|
return flows;
|
|
37895
38430
|
}
|
|
38431
|
+
function csharpMethodScopedAliases(code, seeds, methods, fieldNames) {
|
|
38432
|
+
const lines = code.split(`
|
|
38433
|
+
`);
|
|
38434
|
+
const methodOf = (line) => {
|
|
38435
|
+
let best = -1;
|
|
38436
|
+
let span = Infinity;
|
|
38437
|
+
methods.forEach((m, i2) => {
|
|
38438
|
+
if (line >= m.start_line && line <= m.end_line && m.end_line - m.start_line < span) {
|
|
38439
|
+
best = i2;
|
|
38440
|
+
span = m.end_line - m.start_line;
|
|
38441
|
+
}
|
|
38442
|
+
});
|
|
38443
|
+
return best;
|
|
38444
|
+
};
|
|
38445
|
+
const declRe = /^\s*(?:public|private|protected|internal|static|readonly|const|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*\??)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
38446
|
+
const assignRe = /^\s*(?:this\s*\.\s*)?([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
38447
|
+
const returnRe = /^\s*return\s+(.+?);\s*$/;
|
|
38448
|
+
const KEYWORDS = new Set(["return", "throw", "new", "await", "yield", "else", "case", "using", "var"]);
|
|
38449
|
+
const codeOf = (rhs) => rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
38450
|
+
const local = new Map;
|
|
38451
|
+
const taintedFields = new Set;
|
|
38452
|
+
const returnsTainted = new Set;
|
|
38453
|
+
const localsOf = (m) => {
|
|
38454
|
+
let set = local.get(m);
|
|
38455
|
+
if (!set) {
|
|
38456
|
+
set = new Set;
|
|
38457
|
+
local.set(m, set);
|
|
38458
|
+
}
|
|
38459
|
+
return set;
|
|
38460
|
+
};
|
|
38461
|
+
for (const seed of seeds) {
|
|
38462
|
+
const base = seed.variable.replace(/^this\./, "");
|
|
38463
|
+
if (fieldNames.has(base))
|
|
38464
|
+
taintedFields.add(base);
|
|
38465
|
+
else
|
|
38466
|
+
localsOf(methodOf(seed.line)).add(seed.variable);
|
|
38467
|
+
}
|
|
38468
|
+
const out2 = [];
|
|
38469
|
+
let changed = true;
|
|
38470
|
+
for (let guard = 0;changed && guard < lines.length + 2; guard++) {
|
|
38471
|
+
changed = false;
|
|
38472
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
38473
|
+
let text = lines[i2];
|
|
38474
|
+
const trimmed = text.trimStart();
|
|
38475
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
38476
|
+
continue;
|
|
38477
|
+
if (/^\s*(?:[\w.<>\[\]?]+\s+)?[A-Za-z_]\w*\s*=(?!=)/.test(text) && !/;\s*(?:\/\/.*)?$/.test(text)) {
|
|
38478
|
+
for (let j = i2 + 1;j < lines.length && j <= i2 + 12; j++) {
|
|
38479
|
+
text = text.trimEnd() + " " + lines[j].trim();
|
|
38480
|
+
if (/;\s*(?:\/\/.*)?$/.test(lines[j]))
|
|
38481
|
+
break;
|
|
38482
|
+
}
|
|
38483
|
+
}
|
|
38484
|
+
const m = methodOf(i2 + 1);
|
|
38485
|
+
const here = localsOf(m);
|
|
38486
|
+
const ret = returnRe.exec(text);
|
|
38487
|
+
if (ret && m !== -1 && !returnsTainted.has(methods[m].name)) {
|
|
38488
|
+
const idents2 = codeOf(ret[1]).match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
38489
|
+
if (idents2.some((t) => here.has(t) || taintedFields.has(t))) {
|
|
38490
|
+
returnsTainted.add(methods[m].name);
|
|
38491
|
+
changed = true;
|
|
38492
|
+
}
|
|
38493
|
+
continue;
|
|
38494
|
+
}
|
|
38495
|
+
const d = declRe.exec(text) ?? assignRe.exec(text);
|
|
38496
|
+
if (!d)
|
|
38497
|
+
continue;
|
|
38498
|
+
const [, lhs, rhs] = d;
|
|
38499
|
+
if (KEYWORDS.has(lhs))
|
|
38500
|
+
continue;
|
|
38501
|
+
const isField = fieldNames.has(lhs) && !declRe.test(text);
|
|
38502
|
+
if (isField ? taintedFields.has(lhs) : here.has(lhs))
|
|
38503
|
+
continue;
|
|
38504
|
+
const rhsCode = codeOf(rhs);
|
|
38505
|
+
const idents = rhsCode.match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
38506
|
+
const viaLocal = idents.some((t) => here.has(t));
|
|
38507
|
+
const viaField = idents.some((t) => taintedFields.has(t) && !here.has(t));
|
|
38508
|
+
const viaCall = [...rhsCode.matchAll(/([\p{L}_][\p{L}\p{N}_]*)\s*\(/gu)].some((c) => returnsTainted.has(c[1]));
|
|
38509
|
+
if (!viaLocal && !viaField && !viaCall)
|
|
38510
|
+
continue;
|
|
38511
|
+
if (isField)
|
|
38512
|
+
taintedFields.add(lhs);
|
|
38513
|
+
else
|
|
38514
|
+
here.add(lhs);
|
|
38515
|
+
out2.push({ variable: lhs, line: i2 + 1, carried: !viaLocal });
|
|
38516
|
+
changed = true;
|
|
38517
|
+
}
|
|
38518
|
+
}
|
|
38519
|
+
return out2;
|
|
38520
|
+
}
|
|
37896
38521
|
var _reassignSplitCode;
|
|
37897
38522
|
var _reassignSplitLines;
|
|
37898
38523
|
function splitLinesCached(code) {
|
|
@@ -37903,7 +38528,7 @@ function splitLinesCached(code) {
|
|
|
37903
38528
|
`);
|
|
37904
38529
|
return _reassignSplitLines;
|
|
37905
38530
|
}
|
|
37906
|
-
function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
38531
|
+
function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine, unreachableLines) {
|
|
37907
38532
|
if (!variable || sinkLine - srcLine < 2)
|
|
37908
38533
|
return false;
|
|
37909
38534
|
if (!/^[A-Za-z_][\w]*$/.test(variable))
|
|
@@ -37919,7 +38544,120 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
|
37919
38544
|
const line = lines[i2];
|
|
37920
38545
|
if (!line)
|
|
37921
38546
|
continue;
|
|
37922
|
-
if (
|
|
38547
|
+
if (reGuarded.test(line))
|
|
38548
|
+
return true;
|
|
38549
|
+
if (!reNaked.test(line) && !reSwitchCase.test(line))
|
|
38550
|
+
continue;
|
|
38551
|
+
if (siblingBranchAssignsNonLiteral(lines, i2, variable, strLit, unreachableLines))
|
|
38552
|
+
continue;
|
|
38553
|
+
return true;
|
|
38554
|
+
}
|
|
38555
|
+
return false;
|
|
38556
|
+
}
|
|
38557
|
+
function enclosingBrace(lines, line, col) {
|
|
38558
|
+
let depth = 0;
|
|
38559
|
+
for (let i2 = line;i2 >= 0; i2--) {
|
|
38560
|
+
const text = lines[i2];
|
|
38561
|
+
for (let c = i2 === line ? Math.min(col, text.length) - 1 : text.length - 1;c >= 0; c--) {
|
|
38562
|
+
if (text[c] === "}")
|
|
38563
|
+
depth++;
|
|
38564
|
+
else if (text[c] === "{") {
|
|
38565
|
+
if (depth === 0) {
|
|
38566
|
+
let header = text.slice(0, c).trim();
|
|
38567
|
+
let headerLine = i2;
|
|
38568
|
+
for (let h = i2 - 1;header === "" && h >= 0; h--) {
|
|
38569
|
+
header = lines[h].trim();
|
|
38570
|
+
headerLine = h;
|
|
38571
|
+
}
|
|
38572
|
+
return { line: i2, col: c, header, headerLine };
|
|
38573
|
+
}
|
|
38574
|
+
depth--;
|
|
38575
|
+
}
|
|
38576
|
+
}
|
|
38577
|
+
}
|
|
38578
|
+
return null;
|
|
38579
|
+
}
|
|
38580
|
+
function matchingClose(lines, line, col) {
|
|
38581
|
+
let depth = 0;
|
|
38582
|
+
for (let i2 = line;i2 < lines.length; i2++) {
|
|
38583
|
+
const text = lines[i2];
|
|
38584
|
+
for (let c = i2 === line ? col : 0;c < text.length; c++) {
|
|
38585
|
+
if (text[c] === "{")
|
|
38586
|
+
depth++;
|
|
38587
|
+
else if (text[c] === "}" && --depth === 0)
|
|
38588
|
+
return { line: i2, col: c };
|
|
38589
|
+
}
|
|
38590
|
+
}
|
|
38591
|
+
return null;
|
|
38592
|
+
}
|
|
38593
|
+
function siblingBranchAssignsNonLiteral(lines, idx, variable, strLit, unreachableLines) {
|
|
38594
|
+
let block = enclosingBrace(lines, idx, 0);
|
|
38595
|
+
if (block && /^(?:case\b.*|default\s*):$/.test(block.header)) {
|
|
38596
|
+
block = enclosingBrace(lines, block.line, block.col);
|
|
38597
|
+
}
|
|
38598
|
+
if (!block)
|
|
38599
|
+
return false;
|
|
38600
|
+
let first = block.headerLine;
|
|
38601
|
+
let last;
|
|
38602
|
+
const end = matchingClose(lines, block.line, block.col);
|
|
38603
|
+
if (!end)
|
|
38604
|
+
return false;
|
|
38605
|
+
last = end.line;
|
|
38606
|
+
if (/\bswitch\b/.test(block.header)) {} else if (/^(?:\}\s*)?else\b/.test(block.header) || /^(?:\}\s*)?(?:else\s+)?if\s*\(/.test(block.header)) {
|
|
38607
|
+
let cur = block;
|
|
38608
|
+
for (let guard = 0;guard < 64 && /^(?:\}\s*)?else\b/.test(cur.header); guard++) {
|
|
38609
|
+
let closeLine = cur.headerLine;
|
|
38610
|
+
let closeCol = lines[closeLine].indexOf("}");
|
|
38611
|
+
if (closeCol === -1 || closeCol > lines[closeLine].search(/\belse\b/)) {
|
|
38612
|
+
closeLine--;
|
|
38613
|
+
while (closeLine >= 0 && lines[closeLine].trim() === "")
|
|
38614
|
+
closeLine--;
|
|
38615
|
+
if (closeLine < 0 || !lines[closeLine].trimEnd().endsWith("}"))
|
|
38616
|
+
return false;
|
|
38617
|
+
closeCol = lines[closeLine].lastIndexOf("}");
|
|
38618
|
+
}
|
|
38619
|
+
const prev = enclosingBrace(lines, closeLine, closeCol);
|
|
38620
|
+
if (!prev)
|
|
38621
|
+
return false;
|
|
38622
|
+
cur = prev;
|
|
38623
|
+
first = prev.headerLine;
|
|
38624
|
+
}
|
|
38625
|
+
let close = end;
|
|
38626
|
+
for (let guard = 0;guard < 64; guard++) {
|
|
38627
|
+
let l = close.line;
|
|
38628
|
+
let rest = lines[l].slice(close.col + 1).trim();
|
|
38629
|
+
while (rest === "" && l + 1 < lines.length) {
|
|
38630
|
+
l++;
|
|
38631
|
+
rest = lines[l].trim();
|
|
38632
|
+
}
|
|
38633
|
+
if (!/^else\b/.test(rest))
|
|
38634
|
+
break;
|
|
38635
|
+
let openLine = l;
|
|
38636
|
+
let openCol = lines[l].indexOf("{", l === close.line ? close.col + 1 : 0);
|
|
38637
|
+
if (openCol === -1) {
|
|
38638
|
+
openLine = l + 1;
|
|
38639
|
+
while (openLine < lines.length && lines[openLine].trim() === "")
|
|
38640
|
+
openLine++;
|
|
38641
|
+
if (openLine >= lines.length || !lines[openLine].trimStart().startsWith("{"))
|
|
38642
|
+
break;
|
|
38643
|
+
openCol = lines[openLine].indexOf("{");
|
|
38644
|
+
}
|
|
38645
|
+
const next = matchingClose(lines, openLine, openCol);
|
|
38646
|
+
if (!next)
|
|
38647
|
+
break;
|
|
38648
|
+
close = next;
|
|
38649
|
+
last = next.line;
|
|
38650
|
+
}
|
|
38651
|
+
} else {
|
|
38652
|
+
return false;
|
|
38653
|
+
}
|
|
38654
|
+
const assign = new RegExp(`(?:^|[^\\w.$])${variable}\\s*(?::?=)(?!=)\\s*(.*?)\\s*;?\\s*(?:break\\s*;?)?\\s*$`);
|
|
38655
|
+
const literalOnly = new RegExp(`^${strLit}$`);
|
|
38656
|
+
for (let i2 = first;i2 <= last; i2++) {
|
|
38657
|
+
if (i2 === idx || unreachableLines?.has(i2 + 1))
|
|
38658
|
+
continue;
|
|
38659
|
+
const m = assign.exec(lines[i2]);
|
|
38660
|
+
if (m && m[1] && !literalOnly.test(m[1]))
|
|
37923
38661
|
return true;
|
|
37924
38662
|
}
|
|
37925
38663
|
return false;
|
|
@@ -38048,6 +38786,22 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38048
38786
|
const goFnStart = (line) => enclosingStart(goFnRanges, line);
|
|
38049
38787
|
const csFnRanges = methodRanges(["csharp"]);
|
|
38050
38788
|
const csFnStart = (line) => enclosingStart(csFnRanges, line);
|
|
38789
|
+
const csFieldNames = new Set;
|
|
38790
|
+
if (language === "csharp") {
|
|
38791
|
+
for (const t of types ?? [])
|
|
38792
|
+
for (const f of t.fields ?? [])
|
|
38793
|
+
csFieldNames.add(f.name);
|
|
38794
|
+
}
|
|
38795
|
+
const csDifferentMethod = (variable, sourceLine, otherLine) => {
|
|
38796
|
+
if (csFnRanges.length === 0)
|
|
38797
|
+
return false;
|
|
38798
|
+
const a = csFnStart(sourceLine);
|
|
38799
|
+
const b = csFnStart(otherLine);
|
|
38800
|
+
if (a === -1 || b === -1 || a === b)
|
|
38801
|
+
return false;
|
|
38802
|
+
const base = variable.replace(/^this\./, "").split(".")[0];
|
|
38803
|
+
return !csFieldNames.has(base);
|
|
38804
|
+
};
|
|
38051
38805
|
const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
|
|
38052
38806
|
const aliasSanitizedFor = new Map;
|
|
38053
38807
|
if (language === "python" && typeof code === "string") {
|
|
@@ -38320,7 +39074,29 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38320
39074
|
}
|
|
38321
39075
|
}
|
|
38322
39076
|
}
|
|
38323
|
-
if (
|
|
39077
|
+
if (language === "csharp" && typeof code === "string" && sourcesWithVar.length > 0 && csFnRanges.length > 0) {
|
|
39078
|
+
const csMethods = (types ?? []).flatMap((t) => t.methods);
|
|
39079
|
+
const seeds = sourcesWithVar.map((sv) => ({ variable: sv.variable, line: sv.line }));
|
|
39080
|
+
const methodStartOf = (line) => csFnStart(line);
|
|
39081
|
+
const aliases = csharpMethodScopedAliases(code, seeds, csMethods, csFieldNames);
|
|
39082
|
+
const fileAnchor = sourcesWithVar.reduce((a, sv) => sv.line < a.line ? sv : a, sourcesWithVar[0]);
|
|
39083
|
+
const have = new Set(sourcesWithVar.map((sv) => `${sv.variable}@${methodStartOf(sv.line)}`));
|
|
39084
|
+
for (const alias of aliases) {
|
|
39085
|
+
const fn = methodStartOf(alias.line);
|
|
39086
|
+
const isField = csFieldNames.has(alias.variable);
|
|
39087
|
+
const key = `${alias.variable}@${isField ? "field" : fn}`;
|
|
39088
|
+
if (have.has(key))
|
|
39089
|
+
continue;
|
|
39090
|
+
have.add(key);
|
|
39091
|
+
const inMethod = sourcesWithVar.filter((sv) => methodStartOf(sv.line) === fn && sv.line <= alias.line);
|
|
39092
|
+
const from = !alias.carried && inMethod.length > 0 ? inMethod.reduce((a, sv) => sv.line < a.line ? sv : a, inMethod[0]) : fileAnchor;
|
|
39093
|
+
sourcesWithVar.push({
|
|
39094
|
+
...from,
|
|
39095
|
+
variable: alias.variable,
|
|
39096
|
+
...alias.carried || inMethod.length === 0 ? { line: alias.line } : {}
|
|
39097
|
+
});
|
|
39098
|
+
}
|
|
39099
|
+
} else if ((language === "java" || language === "csharp") && typeof code === "string" && sourcesWithVar.length > 0) {
|
|
38324
39100
|
const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
|
|
38325
39101
|
const derived = buildJavaTaintedVars(code, seedVars);
|
|
38326
39102
|
if (derived.size > 0) {
|
|
@@ -38369,23 +39145,26 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38369
39145
|
const lines = code.split(`
|
|
38370
39146
|
`);
|
|
38371
39147
|
const ctRe = /(\w+)\s*\.\s*CommandText\s*\+?=\s*(.+)/;
|
|
38372
|
-
const
|
|
39148
|
+
const seeded = new Set;
|
|
38373
39149
|
let added = true;
|
|
38374
39150
|
let guard = 0;
|
|
38375
39151
|
while (added && guard < lines.length + 2) {
|
|
38376
39152
|
added = false;
|
|
38377
39153
|
guard++;
|
|
38378
|
-
const known = new Set(sourcesWithVar.map((s) => s.variable));
|
|
38379
39154
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
38380
39155
|
const m = ctRe.exec(lines[i2]);
|
|
38381
39156
|
if (!m)
|
|
38382
39157
|
continue;
|
|
38383
39158
|
const [, obj, rhs] = m;
|
|
38384
|
-
|
|
39159
|
+
const key = `${obj}@${csFnStart(i2 + 1)}`;
|
|
39160
|
+
if (seeded.has(key))
|
|
38385
39161
|
continue;
|
|
38386
|
-
|
|
39162
|
+
const reachable = sourcesWithVar.filter((sv) => sv.line <= i2 + 1 && !csDifferentMethod(sv.variable, sv.line, i2 + 1));
|
|
39163
|
+
if (reachable.length === 0 || !argsReferenceTaintedVar(rhs, reachable))
|
|
38387
39164
|
continue;
|
|
38388
|
-
|
|
39165
|
+
const from = reachable.reduce((a, sv) => sv.line < a.line ? sv : a, reachable[0]);
|
|
39166
|
+
sourcesWithVar.push({ ...from, variable: obj, line: i2 + 1 });
|
|
39167
|
+
seeded.add(key);
|
|
38389
39168
|
added = true;
|
|
38390
39169
|
}
|
|
38391
39170
|
}
|
|
@@ -38404,24 +39183,26 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38404
39183
|
if (builders.size > 0) {
|
|
38405
39184
|
const appendRe = /([\p{L}_][\p{L}\p{N}_]*)\s*\.\s*(?:Append|AppendLine|AppendFormat|Insert)\s*\(([\s\S]*)/u;
|
|
38406
39185
|
const anchor = sourcesWithVar.reduce((a, s) => s.line < a.line ? s : a, sourcesWithVar[0]);
|
|
39186
|
+
const builderSeeded = new Set;
|
|
38407
39187
|
let added = true;
|
|
38408
39188
|
let guard = 0;
|
|
38409
39189
|
while (added && guard < lines.length + 2) {
|
|
38410
39190
|
added = false;
|
|
38411
39191
|
guard++;
|
|
38412
|
-
const known = new Set(sourcesWithVar.map((s) => s.variable));
|
|
38413
39192
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
38414
39193
|
const m = appendRe.exec(lines[i2]);
|
|
38415
39194
|
if (!m)
|
|
38416
39195
|
continue;
|
|
38417
39196
|
const [, obj, args2] = m;
|
|
38418
|
-
|
|
39197
|
+
const seedKey = `${obj}@${csFnStart(i2 + 1)}`;
|
|
39198
|
+
if (!builders.has(obj) || builderSeeded.has(seedKey))
|
|
38419
39199
|
continue;
|
|
38420
39200
|
const appendFn = csFnStart(i2 + 1);
|
|
38421
39201
|
const reachable = sourcesWithVar.filter((sv) => appendFn === -1 || csFnStart(sv.line) === -1 || csFnStart(sv.line) === appendFn);
|
|
38422
39202
|
if (reachable.length > 0 && argsReferenceTaintedVar(args2, reachable)) {
|
|
38423
39203
|
sourcesWithVar.push({ ...anchor, variable: obj, line: i2 + 1 });
|
|
38424
39204
|
builderSeeds.add(`${obj}@${i2 + 1}`);
|
|
39205
|
+
builderSeeded.add(seedKey);
|
|
38425
39206
|
added = true;
|
|
38426
39207
|
}
|
|
38427
39208
|
}
|
|
@@ -38504,6 +39285,8 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38504
39285
|
if (srcFn !== sinkFn)
|
|
38505
39286
|
continue;
|
|
38506
39287
|
}
|
|
39288
|
+
if (csDifferentMethod(source.variable, source.line, sink.line))
|
|
39289
|
+
continue;
|
|
38507
39290
|
if (complex) {
|
|
38508
39291
|
const re = reCache.get(source.variable);
|
|
38509
39292
|
if (!re || !re.test(expr))
|
|
@@ -38517,7 +39300,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38517
39300
|
if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
|
|
38518
39301
|
break;
|
|
38519
39302
|
}
|
|
38520
|
-
if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line)) {
|
|
39303
|
+
if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line, unreachableLines)) {
|
|
38521
39304
|
break;
|
|
38522
39305
|
}
|
|
38523
39306
|
flows.push({
|
|
@@ -38824,7 +39607,14 @@ var CSHARP_NON_ESCAPE_METHODS = new Set([
|
|
|
38824
39607
|
"TryAdd",
|
|
38825
39608
|
"Insert",
|
|
38826
39609
|
"Push",
|
|
38827
|
-
"Enqueue"
|
|
39610
|
+
"Enqueue",
|
|
39611
|
+
"EscapeDataString",
|
|
39612
|
+
"EscapeUriString",
|
|
39613
|
+
"UrlEncode",
|
|
39614
|
+
"UrlPathEncode",
|
|
39615
|
+
"HtmlEncode",
|
|
39616
|
+
"HtmlAttributeEncode",
|
|
39617
|
+
"JavaScriptStringEncode"
|
|
38828
39618
|
]);
|
|
38829
39619
|
function isCSharpNonEscapeCall(call) {
|
|
38830
39620
|
const name2 = call.method_name;
|
|
@@ -39396,6 +40186,7 @@ class InterproceduralPass {
|
|
|
39396
40186
|
}
|
|
39397
40187
|
}
|
|
39398
40188
|
const language = graph.ir.meta.language;
|
|
40189
|
+
const csCredit = language === "csharp" && typeof ctx.code === "string" ? new CSharpCredit(ctx.code, graph.ir.types.flatMap((t) => t.methods)) : null;
|
|
39399
40190
|
if (sinks.length > 0) {
|
|
39400
40191
|
const interProc = analyzeInterprocedural2(graph, sources, sinks, sanitizers, {
|
|
39401
40192
|
taintedVariables: constProp.tainted
|
|
@@ -39428,6 +40219,9 @@ class InterproceduralPass {
|
|
|
39428
40219
|
continue;
|
|
39429
40220
|
if (sanitizerMethodNames.has(method.name))
|
|
39430
40221
|
continue;
|
|
40222
|
+
if (language === "csharp" && typeof ctx.code === "string" && !csharpParamReachesSink(ctx.code, method, edge.taintedArgs, sink, csCredit)) {
|
|
40223
|
+
continue;
|
|
40224
|
+
}
|
|
39431
40225
|
for (const source of sources) {
|
|
39432
40226
|
if (source.line > edge.callLine)
|
|
39433
40227
|
continue;
|
|
@@ -39569,9 +40363,58 @@ class InterproceduralPass {
|
|
|
39569
40363
|
return !sanitizedSinkKeys.has(`${s.line}:${s.type}`);
|
|
39570
40364
|
});
|
|
39571
40365
|
}
|
|
40366
|
+
if (csCredit) {
|
|
40367
|
+
const credited = new Set;
|
|
40368
|
+
filteredAdditionalFlows = filteredAdditionalFlows.filter((f) => {
|
|
40369
|
+
if (f.sink_type !== "external_taint_escape" || !csCredit.creditedOnLine(f.sink_line))
|
|
40370
|
+
return true;
|
|
40371
|
+
credited.add(f.sink_line);
|
|
40372
|
+
return false;
|
|
40373
|
+
});
|
|
40374
|
+
filteredAdditionalSinks = filteredAdditionalSinks.filter((s) => !(s.type === "external_taint_escape" && credited.has(s.line)));
|
|
40375
|
+
}
|
|
39572
40376
|
return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
|
|
39573
40377
|
}
|
|
39574
40378
|
}
|
|
40379
|
+
function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
|
|
40380
|
+
const lines = code.split(/\r?\n/);
|
|
40381
|
+
const params = taintedArgs.length > 0 ? method.parameters.filter((p) => taintedArgs.includes(p.position)) : method.parameters;
|
|
40382
|
+
if (params.length === 0)
|
|
40383
|
+
return true;
|
|
40384
|
+
const derived = new Set(params.map((p) => p.name));
|
|
40385
|
+
const codeOf = (text) => text.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
40386
|
+
const mentionsDerived = (text) => {
|
|
40387
|
+
const bare = codeOf(text);
|
|
40388
|
+
return [...derived].filter((v) => new RegExp(`(?<![\\w.])${v}(?![\\w])`).test(bare));
|
|
40389
|
+
};
|
|
40390
|
+
const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>[\]?]*\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*(.+)$/;
|
|
40391
|
+
const objectWriteRe = /^\s*([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*(?:\+?=(?!=)|\()(.*)$/;
|
|
40392
|
+
for (let i2 = method.startLine + 1;i2 < sink.line; i2++) {
|
|
40393
|
+
const text = lines[i2 - 1] ?? "";
|
|
40394
|
+
const assign = assignRe.exec(text);
|
|
40395
|
+
if (assign) {
|
|
40396
|
+
if (mentionsDerived(assign[2]).length > 0)
|
|
40397
|
+
derived.add(assign[1]);
|
|
40398
|
+
else if (!params.some((p) => p.name === assign[1]))
|
|
40399
|
+
derived.delete(assign[1]);
|
|
40400
|
+
continue;
|
|
40401
|
+
}
|
|
40402
|
+
const write = objectWriteRe.exec(text);
|
|
40403
|
+
if (write && mentionsDerived(write[2]).length > 0)
|
|
40404
|
+
derived.add(write[1]);
|
|
40405
|
+
}
|
|
40406
|
+
let statement = lines[sink.line - 1] ?? "";
|
|
40407
|
+
for (let i2 = sink.line;i2 < lines.length && i2 < sink.line + 6 && !/[;{}]\s*$/.test(statement.trimEnd()); i2++) {
|
|
40408
|
+
statement += " " + (lines[i2] ?? "").trim();
|
|
40409
|
+
}
|
|
40410
|
+
const reaching = mentionsDerived(statement);
|
|
40411
|
+
if (reaching.length === 0)
|
|
40412
|
+
return false;
|
|
40413
|
+
if (!credit)
|
|
40414
|
+
return true;
|
|
40415
|
+
const safe = new Set([...credit.sanitizedVarsAt(sink.type, sink.line), ...credit.guardedVarsAt(sink.type, sink.line)]);
|
|
40416
|
+
return reaching.some((v) => !safe.has(v));
|
|
40417
|
+
}
|
|
39575
40418
|
|
|
39576
40419
|
// ../circle-ir/dist/analysis/passes/dead-code-pass.js
|
|
39577
40420
|
class DeadCodePass {
|
|
@@ -40284,7 +41127,7 @@ class NullDerefPass {
|
|
|
40284
41127
|
}
|
|
40285
41128
|
|
|
40286
41129
|
// ../circle-ir/dist/analysis/passes/resource-leak-pass.js
|
|
40287
|
-
function
|
|
41130
|
+
function escapeRegex3(s) {
|
|
40288
41131
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40289
41132
|
}
|
|
40290
41133
|
var FACTORY_METHOD_NAME_RE = /^(?:open|create|new|get|make|build)[A-Z]/;
|
|
@@ -40475,7 +41318,7 @@ class ResourceLeakPass {
|
|
|
40475
41318
|
return false;
|
|
40476
41319
|
}
|
|
40477
41320
|
isReturnedToCaller(lines, variable, fromLine, toLine) {
|
|
40478
|
-
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${
|
|
41321
|
+
const varRe = new RegExp(`\\breturn\\b[^;]*\\b${escapeRegex3(variable)}\\b`);
|
|
40479
41322
|
for (let l = fromLine;l <= toLine && l <= lines.length; l++) {
|
|
40480
41323
|
if (varRe.test(lines[l - 1] ?? ""))
|
|
40481
41324
|
return true;
|
|
@@ -40483,7 +41326,7 @@ class ResourceLeakPass {
|
|
|
40483
41326
|
return false;
|
|
40484
41327
|
}
|
|
40485
41328
|
fieldStoredName(lines, variable, openLine, toLine) {
|
|
40486
|
-
const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${
|
|
41329
|
+
const thisAssignRe = new RegExp(`\\bthis\\s*\\.\\s*(\\w+)\\s*=\\s*${escapeRegex3(variable)}\\b`);
|
|
40487
41330
|
for (let l = openLine;l <= toLine && l <= lines.length; l++) {
|
|
40488
41331
|
const m = thisAssignRe.exec(lines[l - 1] ?? "");
|
|
40489
41332
|
if (m)
|
|
@@ -40526,7 +41369,7 @@ class ResourceLeakPass {
|
|
|
40526
41369
|
if (fieldNames.has(variable)) {
|
|
40527
41370
|
candidateField = variable;
|
|
40528
41371
|
} else {
|
|
40529
|
-
const thisAssignRe = new RegExp(`(?:\\bthis\\s*\\.\\s*)?(\\w+)\\s*=\\s*${
|
|
41372
|
+
const thisAssignRe = new RegExp(`(?:\\bthis\\s*\\.\\s*)?(\\w+)\\s*=\\s*${escapeRegex3(variable)}\\b`);
|
|
40530
41373
|
for (let l = fromLine;l <= toLine && l <= lines.length; l++) {
|
|
40531
41374
|
const m = thisAssignRe.exec(lines[l - 1] ?? "");
|
|
40532
41375
|
if (m && fieldNames.has(m[1])) {
|
|
@@ -51011,7 +51854,7 @@ var colors = {
|
|
|
51011
51854
|
};
|
|
51012
51855
|
|
|
51013
51856
|
// src/version.ts
|
|
51014
|
-
var version = "4.
|
|
51857
|
+
var version = "4.13.0";
|
|
51015
51858
|
|
|
51016
51859
|
// src/formatters.ts
|
|
51017
51860
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -51563,8 +52406,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
51563
52406
|
|
|
51564
52407
|
// src/build-info.ts
|
|
51565
52408
|
var buildInfo = {
|
|
51566
|
-
gitSha: "
|
|
51567
|
-
builtAt: "2026-10-
|
|
52409
|
+
gitSha: "a45b21b",
|
|
52410
|
+
builtAt: "2026-10-08T13:38:40.827Z"
|
|
51568
52411
|
};
|
|
51569
52412
|
|
|
51570
52413
|
// src/utils/args.ts
|