circle-ir 4.6.0 → 4.7.1
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/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +25 -3
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +16 -1
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +50 -3
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/missing-public-doc-pass.d.ts.map +1 -1
- package/dist/analysis/passes/missing-public-doc-pass.js +6 -1
- package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +10 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts.map +1 -1
- package/dist/analysis/passes/unrestricted-file-upload-pass.js +6 -4
- package/dist/analysis/passes/unrestricted-file-upload-pass.js.map +1 -1
- package/dist/analysis/passes/unused-variable-pass.d.ts.map +1 -1
- package/dist/analysis/passes/unused-variable-pass.js +69 -20
- package/dist/analysis/passes/unused-variable-pass.js.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.js +66 -0
- package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
- package/dist/analysis/passes/weak-hash-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-hash-pass.js +46 -0
- package/dist/analysis/passes/weak-hash-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +203 -25
- package/dist/core/circle-ir-core.cjs +29 -7
- package/dist/core/circle-ir-core.js +29 -7
- package/dist/core/extractors/dfg.js +6 -3
- package/dist/core/extractors/dfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -9918,15 +9918,15 @@ function computeChains(defs, uses) {
|
|
|
9918
9918
|
existing.push(use);
|
|
9919
9919
|
usesByLine.set(use.line, existing);
|
|
9920
9920
|
}
|
|
9921
|
+
const seenChains = /* @__PURE__ */ new Set();
|
|
9921
9922
|
for (const def of defs) {
|
|
9922
9923
|
if (def.kind === "local") {
|
|
9923
9924
|
const usesOnLine = usesByLine.get(def.line) ?? [];
|
|
9924
9925
|
for (const use of usesOnLine) {
|
|
9925
9926
|
if (use.def_id !== null && use.def_id !== def.id) {
|
|
9926
|
-
const
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
if (!exists) {
|
|
9927
|
+
const key = `${use.def_id}\0${def.id}\0${use.variable}`;
|
|
9928
|
+
if (!seenChains.has(key)) {
|
|
9929
|
+
seenChains.add(key);
|
|
9930
9930
|
chains.push({
|
|
9931
9931
|
from_def: use.def_id,
|
|
9932
9932
|
to_def: def.id,
|
|
@@ -12501,8 +12501,11 @@ var DEFAULT_SINKS = [
|
|
|
12501
12501
|
// SAX handler sinks (can lead to XSS in parsed content)
|
|
12502
12502
|
{ method: "startElement", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2] },
|
|
12503
12503
|
{ method: "characters", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
12504
|
-
// Template output sinks
|
|
12505
|
-
|
|
12504
|
+
// Template output sinks. `output` is classless (Java template engines /
|
|
12505
|
+
// Velocity), but Rust `std::process::Command::…output()` is process
|
|
12506
|
+
// execution, not HTML — it already fires the CWE-78 command_injection sink,
|
|
12507
|
+
// so exclude Rust here to drop the spurious xss (cognium-dev#146).
|
|
12508
|
+
{ method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], exclude_languages: ["rust"] },
|
|
12506
12509
|
{ method: "setOutput", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
12507
12510
|
{ method: "writeAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
|
|
12508
12511
|
// AntiSamy specific (SAX filters)
|
|
@@ -13793,7 +13796,10 @@ var DEFAULT_SINKS = [
|
|
|
13793
13796
|
{ method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13794
13797
|
{ method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13795
13798
|
// C# command injection — Process.Start / ProcessStartInfo (CWE-78).
|
|
13796
|
-
|
|
13799
|
+
// Both the single-string overload `Process.Start("sh -c " + x)` and the
|
|
13800
|
+
// `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
|
|
13801
|
+
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13802
|
+
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13797
13803
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13798
13804
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13799
13805
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13834,6 +13840,22 @@ var DEFAULT_SINKS = [
|
|
|
13834
13840
|
// filter is the constructor argument.
|
|
13835
13841
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13836
13842
|
{ method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13843
|
+
// C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
|
|
13844
|
+
// cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
|
|
13845
|
+
// an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
|
|
13846
|
+
// `TypedResults.Redirect(url)` also route through the same method name. Taint-
|
|
13847
|
+
// gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
|
|
13848
|
+
// registered — it rejects non-local URLs and is the documented mitigation.
|
|
13849
|
+
{ method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13850
|
+
{ method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13851
|
+
// C# CRLF / HTTP response-header injection — classic ASP.NET header writers
|
|
13852
|
+
// (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
|
|
13853
|
+
{ method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13854
|
+
{ method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13855
|
+
// C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
|
|
13856
|
+
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13857
|
+
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13858
|
+
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13837
13859
|
// C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
|
|
13838
13860
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13839
13861
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -18562,6 +18584,26 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
18562
18584
|
"Command",
|
|
18563
18585
|
"CommandContext"
|
|
18564
18586
|
]);
|
|
18587
|
+
const ormQueryBuilderMethods = /* @__PURE__ */ new Set([
|
|
18588
|
+
"findMany",
|
|
18589
|
+
"findFirst",
|
|
18590
|
+
"findUnique",
|
|
18591
|
+
"findUniqueOrThrow",
|
|
18592
|
+
"findFirstOrThrow",
|
|
18593
|
+
"findOne",
|
|
18594
|
+
"findAll",
|
|
18595
|
+
"findByPk",
|
|
18596
|
+
"findAndCountAll",
|
|
18597
|
+
"findBy",
|
|
18598
|
+
"findOneBy",
|
|
18599
|
+
"where",
|
|
18600
|
+
"andWhere",
|
|
18601
|
+
"orWhere",
|
|
18602
|
+
"whereIn",
|
|
18603
|
+
"whereNot",
|
|
18604
|
+
"first",
|
|
18605
|
+
"bind"
|
|
18606
|
+
]);
|
|
18565
18607
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
18566
18608
|
for (const san of sanitizers) {
|
|
18567
18609
|
sanitizerMethods.add(san.method);
|
|
@@ -18586,7 +18628,7 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
18586
18628
|
}
|
|
18587
18629
|
const targetMethod = getMethodNode(methodNodes, call.method_name);
|
|
18588
18630
|
if (!targetMethod) {
|
|
18589
|
-
if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name)) {
|
|
18631
|
+
if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
|
|
18590
18632
|
const isBash = graph.ir.meta.language === "bash";
|
|
18591
18633
|
const bashSafeBuiltins = /* @__PURE__ */ new Set([
|
|
18592
18634
|
"echo",
|
|
@@ -26375,6 +26417,7 @@ var LanguageSourcesPass = class {
|
|
|
26375
26417
|
additionalSources.push(...findSetterChainSources(types, code, language));
|
|
26376
26418
|
additionalSources.push(...findJavaScriptAssignmentSources(code, language));
|
|
26377
26419
|
additionalSources.push(...findCSharpRequestSources(code, language));
|
|
26420
|
+
additionalSources.push(...findGoArgvSources(code, language));
|
|
26378
26421
|
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
26379
26422
|
for (const s of jsDOMSinks) {
|
|
26380
26423
|
const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
|
|
@@ -27029,6 +27072,31 @@ function findSetterChainSources(types, sourceCode, language) {
|
|
|
27029
27072
|
}
|
|
27030
27073
|
return sources;
|
|
27031
27074
|
}
|
|
27075
|
+
function findGoArgvSources(sourceCode, language) {
|
|
27076
|
+
if (language !== "go") return [];
|
|
27077
|
+
const sources = [];
|
|
27078
|
+
const lines = sourceCode.split("\n");
|
|
27079
|
+
const rangeRe = /\bfor\b[^;{]*?,\s*([A-Za-z_]\w*)\s*:?=\s*range\s+os\.Args\b/;
|
|
27080
|
+
const assignRe = /\b([A-Za-z_]\w*)\s*:?=\s*os\.Args\b/;
|
|
27081
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
27082
|
+
const line = lines[i2];
|
|
27083
|
+
const rm = rangeRe.exec(line);
|
|
27084
|
+
const m = rm ?? assignRe.exec(line);
|
|
27085
|
+
if (!m) continue;
|
|
27086
|
+
const varName = m[1];
|
|
27087
|
+
const lineNumber = i2 + 1;
|
|
27088
|
+
if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
|
|
27089
|
+
sources.push({
|
|
27090
|
+
type: "io_input",
|
|
27091
|
+
location: `${varName} = os.Args`,
|
|
27092
|
+
severity: "high",
|
|
27093
|
+
line: lineNumber,
|
|
27094
|
+
confidence: 1,
|
|
27095
|
+
variable: varName
|
|
27096
|
+
});
|
|
27097
|
+
}
|
|
27098
|
+
return sources;
|
|
27099
|
+
}
|
|
27032
27100
|
function findCSharpRequestSources(sourceCode, language) {
|
|
27033
27101
|
if (language !== "csharp") return [];
|
|
27034
27102
|
const sources = [];
|
|
@@ -27036,11 +27104,12 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
27036
27104
|
const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
27037
27105
|
const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
|
|
27038
27106
|
const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
|
|
27107
|
+
const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
|
|
27039
27108
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
27040
27109
|
const m = assignRe.exec(lines[i2]);
|
|
27041
27110
|
if (!m) continue;
|
|
27042
27111
|
const [, varName, rhs] = m;
|
|
27043
|
-
const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : null;
|
|
27112
|
+
const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
|
|
27044
27113
|
if (!type) continue;
|
|
27045
27114
|
const lineNumber = i2 + 1;
|
|
27046
27115
|
if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
|
|
@@ -27872,7 +27941,7 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
27872
27941
|
});
|
|
27873
27942
|
}
|
|
27874
27943
|
}
|
|
27875
|
-
const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})
|
|
27944
|
+
const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})(?:[:#%/^,@!*+?=-][^}]*)?\}/g;
|
|
27876
27945
|
let em;
|
|
27877
27946
|
while ((em = envRe.exec(line)) !== null) {
|
|
27878
27947
|
const envVar = em[1] ?? em[2];
|
|
@@ -33928,7 +33997,9 @@ var SinkFilterPass = class {
|
|
|
33928
33997
|
if (language === "csharp") {
|
|
33929
33998
|
const sourceLines = ctx.code.split("\n");
|
|
33930
33999
|
const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
|
|
34000
|
+
const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
|
|
33931
34001
|
filtered = filtered.filter((sink) => {
|
|
34002
|
+
if (sink.type === "xxe" && xxeHardened) return false;
|
|
33932
34003
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
33933
34004
|
for (const { re, type } of CSHARP_SANITIZER_RES) {
|
|
33934
34005
|
if (type === sink.type && re.test(sinkLineText)) return false;
|
|
@@ -37084,6 +37155,8 @@ function isPublicMethod(method, language) {
|
|
|
37084
37155
|
switch (language) {
|
|
37085
37156
|
case "java":
|
|
37086
37157
|
return method.modifiers.includes("public");
|
|
37158
|
+
case "csharp":
|
|
37159
|
+
return method.modifiers.includes("public");
|
|
37087
37160
|
case "javascript":
|
|
37088
37161
|
case "typescript":
|
|
37089
37162
|
case "tsx":
|
|
@@ -37105,7 +37178,7 @@ var MissingPublicDocPass = class {
|
|
|
37105
37178
|
if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
|
|
37106
37179
|
return { missingDocMethods: [], missingDocTypes: [] };
|
|
37107
37180
|
}
|
|
37108
|
-
if (!["java", "javascript", "typescript", "tsx", "python"].includes(language)) {
|
|
37181
|
+
if (!["java", "javascript", "typescript", "tsx", "python", "csharp"].includes(language)) {
|
|
37109
37182
|
return { missingDocMethods: [], missingDocTypes: [] };
|
|
37110
37183
|
}
|
|
37111
37184
|
const lines = code.split("\n");
|
|
@@ -38101,6 +38174,7 @@ var SKIP_NAMES3 = /* @__PURE__ */ new Set([
|
|
|
38101
38174
|
"unknown",
|
|
38102
38175
|
"bigint"
|
|
38103
38176
|
]);
|
|
38177
|
+
var EMPTY_LINES = [];
|
|
38104
38178
|
var UnusedVariablePass = class {
|
|
38105
38179
|
name = "unused-variable";
|
|
38106
38180
|
category = "reliability";
|
|
@@ -38113,6 +38187,29 @@ var UnusedVariablePass = class {
|
|
|
38113
38187
|
const codeLines = code.split("\n");
|
|
38114
38188
|
const unusedVars = [];
|
|
38115
38189
|
const reported = /* @__PURE__ */ new Set();
|
|
38190
|
+
const defsByVar = /* @__PURE__ */ new Map();
|
|
38191
|
+
for (const d of graph.ir.dfg.defs) {
|
|
38192
|
+
const arr = defsByVar.get(d.variable);
|
|
38193
|
+
if (arr) arr.push(d);
|
|
38194
|
+
else defsByVar.set(d.variable, [d]);
|
|
38195
|
+
}
|
|
38196
|
+
const nameLines = /* @__PURE__ */ new Map();
|
|
38197
|
+
const idRe = /[A-Za-z_][A-Za-z0-9_]*/g;
|
|
38198
|
+
for (let i2 = 0; i2 < codeLines.length; i2++) {
|
|
38199
|
+
const line = codeLines[i2];
|
|
38200
|
+
idRe.lastIndex = 0;
|
|
38201
|
+
let seen = null;
|
|
38202
|
+
let m;
|
|
38203
|
+
while ((m = idRe.exec(line)) !== null) {
|
|
38204
|
+
const w = m[0];
|
|
38205
|
+
if (seen === null) seen = /* @__PURE__ */ new Set();
|
|
38206
|
+
if (seen.has(w)) continue;
|
|
38207
|
+
seen.add(w);
|
|
38208
|
+
const arr = nameLines.get(w);
|
|
38209
|
+
if (arr) arr.push(i2 + 1);
|
|
38210
|
+
else nameLines.set(w, [i2 + 1]);
|
|
38211
|
+
}
|
|
38212
|
+
}
|
|
38116
38213
|
for (const def of graph.ir.dfg.defs) {
|
|
38117
38214
|
if (def.kind !== "local") continue;
|
|
38118
38215
|
const variable = def.variable;
|
|
@@ -38123,22 +38220,19 @@ var UnusedVariablePass = class {
|
|
|
38123
38220
|
if (/\bexport\b/.test(lineText)) continue;
|
|
38124
38221
|
const uses = graph.usesOfDef(def.id);
|
|
38125
38222
|
if (uses.length > 0) continue;
|
|
38126
|
-
const otherDefs =
|
|
38127
|
-
(d) => d.variable === variable && d.id !== def.id
|
|
38128
|
-
);
|
|
38223
|
+
const otherDefs = (defsByVar.get(variable) ?? []).filter((d) => d.id !== def.id);
|
|
38129
38224
|
const otherDefLines = new Set(otherDefs.map((d) => d.line));
|
|
38130
|
-
const
|
|
38131
|
-
const
|
|
38225
|
+
const plainName = /^[A-Za-z_][A-Za-z0-9_]*$/.test(variable);
|
|
38226
|
+
const idxLines = plainName ? nameLines.get(variable) ?? EMPTY_LINES : null;
|
|
38227
|
+
const namePattern = plainName ? null : new RegExp(`\\b${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
38132
38228
|
if (otherDefLines.size === 0) {
|
|
38133
|
-
const usedElsewhere = codeLines.some(
|
|
38134
|
-
(line, idx) => idx !== def.line - 1 && namePattern.test(line)
|
|
38135
|
-
);
|
|
38229
|
+
const usedElsewhere = idxLines !== null ? idxLines.some((ln) => ln !== def.line) : codeLines.some((line, idx) => idx !== def.line - 1 && namePattern.test(line));
|
|
38136
38230
|
if (usedElsewhere) continue;
|
|
38137
38231
|
} else {
|
|
38138
38232
|
const lineText2 = codeLines[def.line - 1] ?? "";
|
|
38139
38233
|
const isTrueDeclaration = /\b(?:let|const|var)\s+[\w{[]/.test(lineText2) || /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText2) || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText2) || /\blet\s+(?:mut\s+)?\w/.test(lineText2);
|
|
38140
38234
|
if (isTrueDeclaration) {
|
|
38141
|
-
const usedBeforeNextDef = codeLines.some((line, idx) => {
|
|
38235
|
+
const usedBeforeNextDef = idxLines !== null ? idxLines.some((ln) => ln !== def.line && !otherDefLines.has(ln) && !otherDefs.some((d) => d.line > def.line && d.line < ln)) : codeLines.some((line, idx) => {
|
|
38142
38236
|
const lineNum = idx + 1;
|
|
38143
38237
|
if (lineNum === def.line || otherDefLines.has(lineNum)) return false;
|
|
38144
38238
|
if (!namePattern.test(line)) return false;
|
|
@@ -38146,7 +38240,7 @@ var UnusedVariablePass = class {
|
|
|
38146
38240
|
});
|
|
38147
38241
|
if (usedBeforeNextDef) continue;
|
|
38148
38242
|
} else {
|
|
38149
|
-
const usedOnNonDefLine = codeLines.some((line, idx) => {
|
|
38243
|
+
const usedOnNonDefLine = idxLines !== null ? idxLines.some((ln) => ln !== def.line && !otherDefLines.has(ln)) : codeLines.some((line, idx) => {
|
|
38150
38244
|
const lineNum = idx + 1;
|
|
38151
38245
|
return lineNum !== def.line && !otherDefLines.has(lineNum) && namePattern.test(line);
|
|
38152
38246
|
});
|
|
@@ -41538,6 +41632,28 @@ var WEAK_HASH_NAMES = /* @__PURE__ */ new Set([
|
|
|
41538
41632
|
"sha-1",
|
|
41539
41633
|
"sha1"
|
|
41540
41634
|
]);
|
|
41635
|
+
var CSHARP_WEAK_HASH_CLASSES = /* @__PURE__ */ new Set(["MD5", "SHA1"]);
|
|
41636
|
+
var CSHARP_HASH_FACTORY_RECEIVERS = /* @__PURE__ */ new Set([
|
|
41637
|
+
"HashAlgorithm",
|
|
41638
|
+
"KeyedHashAlgorithm",
|
|
41639
|
+
"HMAC",
|
|
41640
|
+
"CryptoConfig"
|
|
41641
|
+
]);
|
|
41642
|
+
var CSHARP_WEAK_HASH_CTORS = {
|
|
41643
|
+
MD5CryptoServiceProvider: "md5",
|
|
41644
|
+
MD5Cng: "md5",
|
|
41645
|
+
SHA1CryptoServiceProvider: "sha1",
|
|
41646
|
+
SHA1Managed: "sha1",
|
|
41647
|
+
SHA1Cng: "sha1",
|
|
41648
|
+
HMACMD5: "md5",
|
|
41649
|
+
HMACSHA1: "sha1"
|
|
41650
|
+
};
|
|
41651
|
+
function csharpWeakHashAlgo(cleaned) {
|
|
41652
|
+
if (!cleaned) return null;
|
|
41653
|
+
const base = (cleaned.split(".").pop() ?? cleaned).replace(/^hmac/, "");
|
|
41654
|
+
if (base === "sha") return "sha1";
|
|
41655
|
+
return WEAK_HASH_NAMES.has(base) ? base : null;
|
|
41656
|
+
}
|
|
41541
41657
|
var COMMONS_DIGEST_METHODS = /* @__PURE__ */ new Set([
|
|
41542
41658
|
"md2",
|
|
41543
41659
|
"md2Hex",
|
|
@@ -41697,6 +41813,19 @@ var WeakHashPass = class {
|
|
|
41697
41813
|
}
|
|
41698
41814
|
return null;
|
|
41699
41815
|
}
|
|
41816
|
+
if (language === "csharp") {
|
|
41817
|
+
if ((method === "Create" || method === "HashData") && CSHARP_WEAK_HASH_CLASSES.has(receiver)) {
|
|
41818
|
+
return { algorithm: receiver.toLowerCase(), api: `${receiver}.${method}` };
|
|
41819
|
+
}
|
|
41820
|
+
if ((method === "Create" || method === "CreateFromName") && CSHARP_HASH_FACTORY_RECEIVERS.has(receiver)) {
|
|
41821
|
+
const algo = csharpWeakHashAlgo(literalAlgo(call, 0));
|
|
41822
|
+
if (algo) return { algorithm: algo, api: `${receiver}.${method}` };
|
|
41823
|
+
}
|
|
41824
|
+
if (call.is_constructor && CSHARP_WEAK_HASH_CTORS[method]) {
|
|
41825
|
+
return { algorithm: CSHARP_WEAK_HASH_CTORS[method], api: `new ${method}()` };
|
|
41826
|
+
}
|
|
41827
|
+
return null;
|
|
41828
|
+
}
|
|
41700
41829
|
return null;
|
|
41701
41830
|
}
|
|
41702
41831
|
};
|
|
@@ -41758,6 +41887,27 @@ var WEAK_CIPHER_BASES = /* @__PURE__ */ new Set([
|
|
|
41758
41887
|
"seed",
|
|
41759
41888
|
"cast5"
|
|
41760
41889
|
]);
|
|
41890
|
+
var CSHARP_WEAK_CIPHER_CLASSES = /* @__PURE__ */ new Set(["DES", "TripleDES", "RC2", "RC4"]);
|
|
41891
|
+
var CSHARP_CIPHER_FACTORY_RECEIVERS = /* @__PURE__ */ new Set(["SymmetricAlgorithm", "CryptoConfig"]);
|
|
41892
|
+
var CSHARP_WEAK_CIPHER_CTORS = {
|
|
41893
|
+
DESCryptoServiceProvider: "des",
|
|
41894
|
+
TripleDESCryptoServiceProvider: "3des",
|
|
41895
|
+
RC2CryptoServiceProvider: "rc2"
|
|
41896
|
+
};
|
|
41897
|
+
var CSHARP_RSA_FACTORY_CLASSES = /* @__PURE__ */ new Set(["RSA", "DSA"]);
|
|
41898
|
+
var CSHARP_RSA_CTORS = /* @__PURE__ */ new Set([
|
|
41899
|
+
"RSACryptoServiceProvider",
|
|
41900
|
+
"DSACryptoServiceProvider",
|
|
41901
|
+
"RSACng",
|
|
41902
|
+
"DSACng"
|
|
41903
|
+
]);
|
|
41904
|
+
function csharpWeakCipher(cleaned) {
|
|
41905
|
+
if (!cleaned) return null;
|
|
41906
|
+
const base = (cleaned.split(".").pop() ?? cleaned).toLowerCase();
|
|
41907
|
+
if (base === "tripledes" || base === "desede") return "3des";
|
|
41908
|
+
if (base === "arc4") return "rc4";
|
|
41909
|
+
return WEAK_CIPHER_BASES.has(base) ? base : null;
|
|
41910
|
+
}
|
|
41761
41911
|
function classifyJavaCipherSpec(spec) {
|
|
41762
41912
|
const parts2 = spec.split("/").map((p) => p.trim().toLowerCase());
|
|
41763
41913
|
const base = parts2[0] ?? "";
|
|
@@ -42149,6 +42299,34 @@ var WeakCryptoPass = class {
|
|
|
42149
42299
|
}
|
|
42150
42300
|
return out2;
|
|
42151
42301
|
}
|
|
42302
|
+
if (language === "csharp") {
|
|
42303
|
+
if (method === "Create" && CSHARP_WEAK_CIPHER_CLASSES.has(receiver)) {
|
|
42304
|
+
const base = receiver === "TripleDES" ? "3des" : receiver.toLowerCase();
|
|
42305
|
+
out2.push({ issue: "weak-cipher", detail: base, api: `${receiver}.Create` });
|
|
42306
|
+
}
|
|
42307
|
+
if ((method === "Create" || method === "CreateFromName") && CSHARP_CIPHER_FACTORY_RECEIVERS.has(receiver)) {
|
|
42308
|
+
const base = csharpWeakCipher(literalAlgo2(call, 0));
|
|
42309
|
+
if (base) out2.push({ issue: "weak-cipher", detail: base, api: `${receiver}.${method}` });
|
|
42310
|
+
}
|
|
42311
|
+
if (call.is_constructor && CSHARP_WEAK_CIPHER_CTORS[method]) {
|
|
42312
|
+
out2.push({ issue: "weak-cipher", detail: CSHARP_WEAK_CIPHER_CTORS[method], api: `new ${method}()` });
|
|
42313
|
+
}
|
|
42314
|
+
const rsaFactory = method === "Create" && CSHARP_RSA_FACTORY_CLASSES.has(receiver);
|
|
42315
|
+
const rsaCtor = call.is_constructor && CSHARP_RSA_CTORS.has(method);
|
|
42316
|
+
if (rsaFactory || rsaCtor) {
|
|
42317
|
+
const sizeArg = call.arguments.find((a) => a.position === 0);
|
|
42318
|
+
const expr = (sizeArg?.literal ?? sizeArg?.expression ?? "").trim();
|
|
42319
|
+
const n = parseInt(expr, 10);
|
|
42320
|
+
if (Number.isFinite(n) && n > 0 && n < 2048) {
|
|
42321
|
+
out2.push({
|
|
42322
|
+
issue: "weak-rsa-key",
|
|
42323
|
+
detail: String(n),
|
|
42324
|
+
api: rsaCtor ? `new ${method}()` : `${receiver}.Create`
|
|
42325
|
+
});
|
|
42326
|
+
}
|
|
42327
|
+
}
|
|
42328
|
+
return out2;
|
|
42329
|
+
}
|
|
42152
42330
|
return out2;
|
|
42153
42331
|
}
|
|
42154
42332
|
};
|
|
@@ -42832,8 +43010,7 @@ var InfoDisclosureStacktracePass = class {
|
|
|
42832
43010
|
// src/analysis/passes/unrestricted-file-upload-pass.ts
|
|
42833
43011
|
var UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
|
|
42834
43012
|
var FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
|
|
42835
|
-
function lineWindow(
|
|
42836
|
-
const lines = code.split("\n");
|
|
43013
|
+
function lineWindow(lines, startLine, endLine) {
|
|
42837
43014
|
const s = Math.max(0, startLine - 1);
|
|
42838
43015
|
const e = Math.min(lines.length, endLine);
|
|
42839
43016
|
return lines.slice(s, e).join("\n");
|
|
@@ -42853,10 +43030,11 @@ var UnrestrictedFileUploadPass = class {
|
|
|
42853
43030
|
const { graph, language, code } = ctx;
|
|
42854
43031
|
const file = graph.ir.meta.file;
|
|
42855
43032
|
const findings = [];
|
|
43033
|
+
const codeLines = code.split("\n");
|
|
42856
43034
|
const safeFunctionRanges = [];
|
|
42857
43035
|
for (const t of graph.ir.types) {
|
|
42858
43036
|
for (const m of t.methods) {
|
|
42859
|
-
const body2 = lineWindow(
|
|
43037
|
+
const body2 = lineWindow(codeLines, m.start_line, m.end_line);
|
|
42860
43038
|
if (FILE_SAFE_CALL_RE.test(body2)) {
|
|
42861
43039
|
safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
|
|
42862
43040
|
}
|
|
@@ -42866,7 +43044,7 @@ var UnrestrictedFileUploadPass = class {
|
|
|
42866
43044
|
for (const r of safeFunctionRanges) {
|
|
42867
43045
|
if (line >= r.start && line <= r.end) return true;
|
|
42868
43046
|
}
|
|
42869
|
-
const win = lineWindow(
|
|
43047
|
+
const win = lineWindow(codeLines, Math.max(1, line - 20), line + 5);
|
|
42870
43048
|
return FILE_SAFE_CALL_RE.test(win);
|
|
42871
43049
|
};
|
|
42872
43050
|
if (language === "java") {
|
|
@@ -9964,15 +9964,15 @@ function computeChains(defs, uses) {
|
|
|
9964
9964
|
existing.push(use);
|
|
9965
9965
|
usesByLine.set(use.line, existing);
|
|
9966
9966
|
}
|
|
9967
|
+
const seenChains = /* @__PURE__ */ new Set();
|
|
9967
9968
|
for (const def of defs) {
|
|
9968
9969
|
if (def.kind === "local") {
|
|
9969
9970
|
const usesOnLine = usesByLine.get(def.line) ?? [];
|
|
9970
9971
|
for (const use of usesOnLine) {
|
|
9971
9972
|
if (use.def_id !== null && use.def_id !== def.id) {
|
|
9972
|
-
const
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
if (!exists) {
|
|
9973
|
+
const key = `${use.def_id}\0${def.id}\0${use.variable}`;
|
|
9974
|
+
if (!seenChains.has(key)) {
|
|
9975
|
+
seenChains.add(key);
|
|
9976
9976
|
chains.push({
|
|
9977
9977
|
from_def: use.def_id,
|
|
9978
9978
|
to_def: def.id,
|
|
@@ -11896,8 +11896,11 @@ var DEFAULT_SINKS = [
|
|
|
11896
11896
|
// SAX handler sinks (can lead to XSS in parsed content)
|
|
11897
11897
|
{ method: "startElement", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2] },
|
|
11898
11898
|
{ method: "characters", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11899
|
-
// Template output sinks
|
|
11900
|
-
|
|
11899
|
+
// Template output sinks. `output` is classless (Java template engines /
|
|
11900
|
+
// Velocity), but Rust `std::process::Command::…output()` is process
|
|
11901
|
+
// execution, not HTML — it already fires the CWE-78 command_injection sink,
|
|
11902
|
+
// so exclude Rust here to drop the spurious xss (cognium-dev#146).
|
|
11903
|
+
{ method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], exclude_languages: ["rust"] },
|
|
11901
11904
|
{ method: "setOutput", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11902
11905
|
{ method: "writeAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
|
|
11903
11906
|
// AntiSamy specific (SAX filters)
|
|
@@ -13188,7 +13191,10 @@ var DEFAULT_SINKS = [
|
|
|
13188
13191
|
{ method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13189
13192
|
{ method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13190
13193
|
// C# command injection — Process.Start / ProcessStartInfo (CWE-78).
|
|
13191
|
-
|
|
13194
|
+
// Both the single-string overload `Process.Start("sh -c " + x)` and the
|
|
13195
|
+
// `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
|
|
13196
|
+
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13197
|
+
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13192
13198
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13193
13199
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13194
13200
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13229,6 +13235,22 @@ var DEFAULT_SINKS = [
|
|
|
13229
13235
|
// filter is the constructor argument.
|
|
13230
13236
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13231
13237
|
{ method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13238
|
+
// C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
|
|
13239
|
+
// cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
|
|
13240
|
+
// an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
|
|
13241
|
+
// `TypedResults.Redirect(url)` also route through the same method name. Taint-
|
|
13242
|
+
// gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
|
|
13243
|
+
// registered — it rejects non-local URLs and is the documented mitigation.
|
|
13244
|
+
{ method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13245
|
+
{ method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13246
|
+
// C# CRLF / HTTP response-header injection — classic ASP.NET header writers
|
|
13247
|
+
// (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
|
|
13248
|
+
{ method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13249
|
+
{ method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13250
|
+
// C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
|
|
13251
|
+
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13252
|
+
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13253
|
+
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13232
13254
|
// C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
|
|
13233
13255
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13234
13256
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -9898,15 +9898,15 @@ function computeChains(defs, uses) {
|
|
|
9898
9898
|
existing.push(use);
|
|
9899
9899
|
usesByLine.set(use.line, existing);
|
|
9900
9900
|
}
|
|
9901
|
+
const seenChains = /* @__PURE__ */ new Set();
|
|
9901
9902
|
for (const def of defs) {
|
|
9902
9903
|
if (def.kind === "local") {
|
|
9903
9904
|
const usesOnLine = usesByLine.get(def.line) ?? [];
|
|
9904
9905
|
for (const use of usesOnLine) {
|
|
9905
9906
|
if (use.def_id !== null && use.def_id !== def.id) {
|
|
9906
|
-
const
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
if (!exists) {
|
|
9907
|
+
const key = `${use.def_id}\0${def.id}\0${use.variable}`;
|
|
9908
|
+
if (!seenChains.has(key)) {
|
|
9909
|
+
seenChains.add(key);
|
|
9910
9910
|
chains.push({
|
|
9911
9911
|
from_def: use.def_id,
|
|
9912
9912
|
to_def: def.id,
|
|
@@ -11830,8 +11830,11 @@ var DEFAULT_SINKS = [
|
|
|
11830
11830
|
// SAX handler sinks (can lead to XSS in parsed content)
|
|
11831
11831
|
{ method: "startElement", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2] },
|
|
11832
11832
|
{ method: "characters", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11833
|
-
// Template output sinks
|
|
11834
|
-
|
|
11833
|
+
// Template output sinks. `output` is classless (Java template engines /
|
|
11834
|
+
// Velocity), but Rust `std::process::Command::…output()` is process
|
|
11835
|
+
// execution, not HTML — it already fires the CWE-78 command_injection sink,
|
|
11836
|
+
// so exclude Rust here to drop the spurious xss (cognium-dev#146).
|
|
11837
|
+
{ method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], exclude_languages: ["rust"] },
|
|
11835
11838
|
{ method: "setOutput", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
11836
11839
|
{ method: "writeAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
|
|
11837
11840
|
// AntiSamy specific (SAX filters)
|
|
@@ -13122,7 +13125,10 @@ var DEFAULT_SINKS = [
|
|
|
13122
13125
|
{ method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13123
13126
|
{ method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13124
13127
|
// C# command injection — Process.Start / ProcessStartInfo (CWE-78).
|
|
13125
|
-
|
|
13128
|
+
// Both the single-string overload `Process.Start("sh -c " + x)` and the
|
|
13129
|
+
// `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
|
|
13130
|
+
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13131
|
+
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13126
13132
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13127
13133
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13128
13134
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13163,6 +13169,22 @@ var DEFAULT_SINKS = [
|
|
|
13163
13169
|
// filter is the constructor argument.
|
|
13164
13170
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13165
13171
|
{ method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13172
|
+
// C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
|
|
13173
|
+
// cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
|
|
13174
|
+
// an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
|
|
13175
|
+
// `TypedResults.Redirect(url)` also route through the same method name. Taint-
|
|
13176
|
+
// gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
|
|
13177
|
+
// registered — it rejects non-local URLs and is the documented mitigation.
|
|
13178
|
+
{ method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13179
|
+
{ method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13180
|
+
// C# CRLF / HTTP response-header injection — classic ASP.NET header writers
|
|
13181
|
+
// (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
|
|
13182
|
+
{ method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13183
|
+
{ method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13184
|
+
// C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
|
|
13185
|
+
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13186
|
+
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13187
|
+
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13166
13188
|
// C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
|
|
13167
13189
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13168
13190
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -1014,15 +1014,18 @@ function computeChains(defs, uses) {
|
|
|
1014
1014
|
}
|
|
1015
1015
|
// For each definition, find uses on the same line that might be in its initializer
|
|
1016
1016
|
// This is a heuristic - more precise would require tracking def-use relationships in AST
|
|
1017
|
+
// Dedup via a Set keyed on (from_def, to_def, via) — an `Array.some` scan per
|
|
1018
|
+
// insertion was O(chains²) on large files (cognium-ai#305).
|
|
1019
|
+
const seenChains = new Set();
|
|
1017
1020
|
for (const def of defs) {
|
|
1018
1021
|
if (def.kind === 'local') {
|
|
1019
1022
|
const usesOnLine = usesByLine.get(def.line) ?? [];
|
|
1020
1023
|
for (const use of usesOnLine) {
|
|
1021
1024
|
// If this use has a reaching def, create a chain
|
|
1022
1025
|
if (use.def_id !== null && use.def_id !== def.id) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
+
const key = `${use.def_id}${def.id}${use.variable}`;
|
|
1027
|
+
if (!seenChains.has(key)) {
|
|
1028
|
+
seenChains.add(key);
|
|
1026
1029
|
chains.push({
|
|
1027
1030
|
from_def: use.def_id,
|
|
1028
1031
|
to_def: def.id,
|