cognium-dev 4.8.2 → 4.9.8

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.
Files changed (2) hide show
  1. package/dist/cli.js +466 -46
  2. package/package.json +6 -6
package/dist/cli.js CHANGED
@@ -3646,6 +3646,7 @@ function extractCSharpTypes(tree, cache) {
3646
3646
  const nameNode = node.childForFieldName("name");
3647
3647
  const body2 = node.childForFieldName("body");
3648
3648
  const methods = [];
3649
+ const fields = extractCSharpFields(body2);
3649
3650
  if (body2) {
3650
3651
  for (let i2 = 0;i2 < body2.childCount; i2++) {
3651
3652
  const m = body2.child(i2);
@@ -3665,7 +3666,7 @@ function extractCSharpTypes(tree, cache) {
3665
3666
  parameters.push({
3666
3667
  name: getNodeText(pName),
3667
3668
  type: pType ? getNodeText(pType) : null,
3668
- annotations: [],
3669
+ annotations: extractCSharpParamAnnotations(pnode),
3669
3670
  line: pnode.startPosition.row + 1
3670
3671
  });
3671
3672
  }
@@ -3691,7 +3692,7 @@ function extractCSharpTypes(tree, cache) {
3691
3692
  implements: [],
3692
3693
  annotations: [],
3693
3694
  methods,
3694
- fields: [],
3695
+ fields,
3695
3696
  start_line: node.startPosition.row + 1,
3696
3697
  end_line: node.endPosition.row + 1
3697
3698
  });
@@ -3699,6 +3700,67 @@ function extractCSharpTypes(tree, cache) {
3699
3700
  }
3700
3701
  return types;
3701
3702
  }
3703
+ function extractCSharpFields(body2) {
3704
+ const fields = [];
3705
+ if (!body2)
3706
+ return fields;
3707
+ for (let i2 = 0;i2 < body2.childCount; i2++) {
3708
+ const c = body2.child(i2);
3709
+ if (!c)
3710
+ continue;
3711
+ if (c.type === "field_declaration") {
3712
+ let varDecl = null;
3713
+ for (let k = 0;k < c.childCount; k++) {
3714
+ const cc = c.child(k);
3715
+ if (cc?.type === "variable_declaration") {
3716
+ varDecl = cc;
3717
+ break;
3718
+ }
3719
+ }
3720
+ if (!varDecl)
3721
+ continue;
3722
+ const typeNode = varDecl.childForFieldName("type");
3723
+ const type = typeNode ? getNodeText(typeNode) : null;
3724
+ const modifiers = extractCSharpModifiers(c);
3725
+ for (let k = 0;k < varDecl.childCount; k++) {
3726
+ const decl = varDecl.child(k);
3727
+ if (decl?.type !== "variable_declarator")
3728
+ continue;
3729
+ const nameNode = decl.childForFieldName("name");
3730
+ fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
3731
+ }
3732
+ } else if (c.type === "property_declaration") {
3733
+ const nameNode = c.childForFieldName("name");
3734
+ if (!nameNode)
3735
+ continue;
3736
+ const typeNode = c.childForFieldName("type");
3737
+ fields.push({
3738
+ name: getNodeText(nameNode),
3739
+ type: typeNode ? getNodeText(typeNode) : null,
3740
+ modifiers: extractCSharpModifiers(c),
3741
+ annotations: []
3742
+ });
3743
+ }
3744
+ }
3745
+ return fields;
3746
+ }
3747
+ function extractCSharpParamAnnotations(param) {
3748
+ const out2 = [];
3749
+ for (let i2 = 0;i2 < param.childCount; i2++) {
3750
+ const list = param.child(i2);
3751
+ if (list?.type !== "attribute_list")
3752
+ continue;
3753
+ for (let j = 0;j < list.childCount; j++) {
3754
+ const attr = list.child(j);
3755
+ if (attr?.type !== "attribute")
3756
+ continue;
3757
+ const name2 = attr.childForFieldName("name");
3758
+ if (name2)
3759
+ out2.push(getNodeText(name2));
3760
+ }
3761
+ }
3762
+ return out2;
3763
+ }
3702
3764
  function extractCSharpModifiers(node) {
3703
3765
  const mods = [];
3704
3766
  for (let i2 = 0;i2 < node.childCount; i2++) {
@@ -12464,6 +12526,9 @@ var DEFAULT_SINKS = [
12464
12526
  { method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12465
12527
  { method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12466
12528
  { method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12529
+ { method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12530
+ { method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
12531
+ { method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12467
12532
  { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12468
12533
  { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12469
12534
  { method: "Deserialize", class: "SoapFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
@@ -13271,7 +13336,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
13271
13336
  if (skipMethods.includes(method.name))
13272
13337
  continue;
13273
13338
  for (const param of method.parameters) {
13274
- const isTaintable = param.type ? isInterproceduralTaintableType(param.type, language) : true;
13339
+ const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
13340
+ const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
13275
13341
  if (isTaintable) {
13276
13342
  const paramLine = param.line ?? method.start_line;
13277
13343
  sources.push({
@@ -13384,6 +13450,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
13384
13450
  }
13385
13451
  return result;
13386
13452
  }
13453
+ var CSHARP_BINDING_ATTRS = new Set([
13454
+ "FromBody",
13455
+ "FromQuery",
13456
+ "FromRoute",
13457
+ "FromForm",
13458
+ "FromHeader"
13459
+ ]);
13387
13460
  function isInterproceduralTaintableType(typeName, language) {
13388
13461
  const baseType = typeName.split("<")[0].trim();
13389
13462
  const excludedTypes = [
@@ -13688,6 +13761,171 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
13688
13761
  }
13689
13762
  return false;
13690
13763
  }
13764
+ function stripCsLiterals(line) {
13765
+ return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
13766
+ }
13767
+ function csBraceDepthBefore(lines, idx) {
13768
+ let depth = 0;
13769
+ for (let i2 = 0;i2 < idx && i2 < lines.length; i2++) {
13770
+ for (const ch of stripCsLiterals(lines[i2])) {
13771
+ if (ch === "{")
13772
+ depth++;
13773
+ else if (ch === "}")
13774
+ depth--;
13775
+ }
13776
+ }
13777
+ return depth;
13778
+ }
13779
+ function netBraces(fragment) {
13780
+ let n = 0;
13781
+ for (const ch of fragment) {
13782
+ if (ch === "{")
13783
+ n++;
13784
+ else if (ch === "}")
13785
+ n--;
13786
+ }
13787
+ return n;
13788
+ }
13789
+ function splitCsIf(line) {
13790
+ const m = /\bif\s*\(/.exec(line);
13791
+ if (!m)
13792
+ return null;
13793
+ const ifCol = m.index;
13794
+ let i2 = m.index + m[0].length;
13795
+ let depth = 1;
13796
+ let inStr = false;
13797
+ let strCh = "";
13798
+ for (;i2 < line.length; i2++) {
13799
+ const c = line[i2];
13800
+ if (inStr) {
13801
+ if (c === "\\") {
13802
+ i2++;
13803
+ continue;
13804
+ }
13805
+ if (c === strCh)
13806
+ inStr = false;
13807
+ continue;
13808
+ }
13809
+ if (c === '"' || c === "'") {
13810
+ inStr = true;
13811
+ strCh = c;
13812
+ continue;
13813
+ }
13814
+ if (c === "(")
13815
+ depth++;
13816
+ else if (c === ")") {
13817
+ depth--;
13818
+ if (depth === 0)
13819
+ break;
13820
+ }
13821
+ }
13822
+ if (depth !== 0)
13823
+ return null;
13824
+ return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
13825
+ }
13826
+ function csEqualityGuard(cond) {
13827
+ const c = cond.trim();
13828
+ let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
13829
+ if (m)
13830
+ return { op: m[2], exprSide: m[1].trim() };
13831
+ m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
13832
+ if (m)
13833
+ return { op: m[1], exprSide: m[2].trim() };
13834
+ return null;
13835
+ }
13836
+ var CS_IDENT_RE = /[A-Za-z_]\w*/g;
13837
+ function csIdentifiers(expr) {
13838
+ return new Set(expr.match(CS_IDENT_RE) ?? []);
13839
+ }
13840
+ function csThenBlock(lines, ifIdx, rest) {
13841
+ const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
13842
+ let firstIdx;
13843
+ let firstText;
13844
+ if (rest.trim()) {
13845
+ firstIdx = ifIdx;
13846
+ firstText = rest;
13847
+ } else {
13848
+ let j = ifIdx + 1;
13849
+ while (j < lines.length && stripCsLiterals(lines[j]).trim() === "")
13850
+ j++;
13851
+ firstIdx = j;
13852
+ firstText = lines[j] ?? "";
13853
+ }
13854
+ if (firstText.trim().startsWith("{")) {
13855
+ let depth = 0;
13856
+ let started = false;
13857
+ let endLine = lines.length - 1;
13858
+ let body2 = "";
13859
+ for (let i2 = firstIdx;i2 < lines.length; i2++) {
13860
+ const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
13861
+ let broke = false;
13862
+ for (const ch of stripped) {
13863
+ if (ch === "{") {
13864
+ depth++;
13865
+ started = true;
13866
+ if (depth === 1)
13867
+ continue;
13868
+ } else if (ch === "}") {
13869
+ depth--;
13870
+ if (depth === 0) {
13871
+ endLine = i2;
13872
+ broke = true;
13873
+ break;
13874
+ }
13875
+ }
13876
+ if (started && depth >= 1)
13877
+ body2 += ch;
13878
+ }
13879
+ if (broke)
13880
+ break;
13881
+ if (started)
13882
+ body2 += " ";
13883
+ }
13884
+ return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
13885
+ }
13886
+ return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
13887
+ }
13888
+ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
13889
+ if (language !== "csharp")
13890
+ return false;
13891
+ if (pattern.type !== "ssrf")
13892
+ return false;
13893
+ if (!sourceLines || sourceLines.length === 0)
13894
+ return false;
13895
+ const candidates = new Set;
13896
+ for (const a of call.arguments) {
13897
+ if (a.variable)
13898
+ candidates.add(a.variable);
13899
+ for (const id of csIdentifiers(a.expression ?? ""))
13900
+ candidates.add(id);
13901
+ }
13902
+ if (candidates.size === 0)
13903
+ return false;
13904
+ const sinkIdx = call.location.line - 1;
13905
+ const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
13906
+ for (let i2 = 0;i2 < sinkIdx; i2++) {
13907
+ const parts2 = splitCsIf(sourceLines[i2]);
13908
+ if (!parts2)
13909
+ continue;
13910
+ const guard = csEqualityGuard(parts2.cond);
13911
+ if (!guard)
13912
+ continue;
13913
+ const guardIds = csIdentifiers(guard.exprSide);
13914
+ if (![...guardIds].some((id) => candidates.has(id)))
13915
+ continue;
13916
+ const block = csThenBlock(sourceLines, i2, parts2.rest);
13917
+ if (guard.op === "==") {
13918
+ if (sinkIdx >= block.start && sinkIdx <= block.end)
13919
+ return true;
13920
+ } else {
13921
+ const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
13922
+ if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
13923
+ return true;
13924
+ }
13925
+ }
13926
+ }
13927
+ return false;
13928
+ }
13691
13929
  function isSafeRustCommandCall(call, pattern, language) {
13692
13930
  if (language !== "rust")
13693
13931
  return false;
@@ -13981,6 +14219,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
13981
14219
  if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
13982
14220
  continue;
13983
14221
  }
14222
+ if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
14223
+ continue;
14224
+ }
13984
14225
  if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
13985
14226
  continue;
13986
14227
  }
@@ -25211,6 +25452,8 @@ class LanguageSourcesPass {
25211
25452
  additionalSources.push(...findSetterChainSources(types, code, language));
25212
25453
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
25213
25454
  additionalSources.push(...findCSharpRequestSources(code, language));
25455
+ additionalSources.push(...findCSharpBindingAttributeSources(code, language));
25456
+ additionalSources.push(...findCSharpMinimalApiSources(code, language));
25214
25457
  additionalSources.push(...findGoArgvSources(code, language));
25215
25458
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
25216
25459
  for (const s of jsDOMSinks) {
@@ -25875,6 +26118,104 @@ function findCSharpRequestSources(sourceCode, language) {
25875
26118
  }
25876
26119
  return sources;
25877
26120
  }
26121
+ var CSHARP_MINIMAL_API_BINDING = new Map([
26122
+ ["FromBody", "http_body"],
26123
+ ["FromForm", "http_body"],
26124
+ ["FromQuery", "http_param"],
26125
+ ["FromRoute", "http_path"],
26126
+ ["FromHeader", "http_param"]
26127
+ ]);
26128
+ var CSHARP_NON_INPUT_TYPES = new Set([
26129
+ "HttpContext",
26130
+ "HttpRequest",
26131
+ "HttpResponse",
26132
+ "CancellationToken",
26133
+ "ClaimsPrincipal",
26134
+ "ILogger",
26135
+ "IFormFileCollection"
26136
+ ]);
26137
+ function findCSharpBindingAttributeSources(sourceCode, language) {
26138
+ if (language !== "csharp")
26139
+ return [];
26140
+ const sources = [];
26141
+ const lines = sourceCode.split(`
26142
+ `);
26143
+ const re = /\[\s*(FromQuery|FromBody|FromForm|FromRoute|FromHeader)(?:\s*\([^)]*\))?\s*\]\s*(?:\[[^\]]*\]\s*)*[\w.<>[\]?]+\s+([A-Za-z_]\w*)/g;
26144
+ for (let i2 = 0;i2 < lines.length; i2++) {
26145
+ const lineRe = new RegExp(re.source, "g");
26146
+ let m;
26147
+ while ((m = lineRe.exec(lines[i2])) !== null) {
26148
+ const attr = m[1];
26149
+ const name2 = m[2];
26150
+ const type = attr === "FromBody" || attr === "FromForm" ? "http_body" : attr === "FromRoute" ? "http_path" : "http_param";
26151
+ if (sources.some((s) => s.line === i2 + 1 && s.variable === name2))
26152
+ continue;
26153
+ sources.push({
26154
+ type,
26155
+ location: `[${attr}] ${name2}`,
26156
+ severity: "high",
26157
+ line: i2 + 1,
26158
+ confidence: 1,
26159
+ variable: name2
26160
+ });
26161
+ }
26162
+ }
26163
+ return sources;
26164
+ }
26165
+ function findCSharpMinimalApiSources(sourceCode, language) {
26166
+ if (language !== "csharp")
26167
+ return [];
26168
+ const sources = [];
26169
+ const lines = sourceCode.split(`
26170
+ `);
26171
+ const mapRe = /\bMap(?:Get|Post|Put|Delete|Patch)\s*\(\s*(?:@?"[^"]*"|[\w.]+)\s*,\s*(?:\[[^\]]*\]\s*)?(?:async\s*)?\(([^)]*)\)\s*=>/;
26172
+ for (let i2 = 0;i2 < lines.length; i2++) {
26173
+ const m = mapRe.exec(lines[i2]);
26174
+ if (!m || !m[1].trim())
26175
+ continue;
26176
+ for (const rawParam of m[1].split(",")) {
26177
+ const seed = classifyCSharpLambdaParam(rawParam);
26178
+ if (!seed)
26179
+ continue;
26180
+ if (sources.some((s) => s.line === i2 + 1 && s.variable === seed.name))
26181
+ continue;
26182
+ sources.push({
26183
+ type: seed.type,
26184
+ location: `${seed.name} (Minimal API ${seed.via})`,
26185
+ severity: "high",
26186
+ line: i2 + 1,
26187
+ confidence: 1,
26188
+ variable: seed.name
26189
+ });
26190
+ }
26191
+ }
26192
+ return sources;
26193
+ }
26194
+ function classifyCSharpLambdaParam(raw) {
26195
+ const attrs = [...raw.matchAll(/\[([^\]]*)\]/g)].map((a) => a[1].split("(")[0].trim());
26196
+ if (attrs.includes("FromServices"))
26197
+ return null;
26198
+ if (attrs.some((a) => CSHARP_MINIMAL_API_BINDING.has(a)))
26199
+ return null;
26200
+ const noAttr = raw.replace(/\[[^\]]*\]/g, "").trim();
26201
+ const parts2 = noAttr.split(/\s+/).filter(Boolean);
26202
+ if (parts2.length < 2)
26203
+ return null;
26204
+ const name2 = parts2[parts2.length - 1];
26205
+ if (!/^[A-Za-z_]\w*$/.test(name2))
26206
+ return null;
26207
+ const baseType = (parts2[parts2.length - 2] ?? "").replace(/[?\[\]]/g, "").split("<")[0].split(".").pop() ?? "";
26208
+ if (CSHARP_NON_INPUT_TYPES.has(baseType))
26209
+ return null;
26210
+ for (const a of attrs) {
26211
+ const t = CSHARP_MINIMAL_API_BINDING.get(a);
26212
+ if (t)
26213
+ return { type: t, name: name2, via: `[${a}]` };
26214
+ }
26215
+ if (baseType === "string")
26216
+ return { type: "http_param", name: name2, via: "string param" };
26217
+ return null;
26218
+ }
25878
26219
  function findJavaScriptAssignmentSources(sourceCode, language) {
25879
26220
  if (!["javascript", "typescript"].includes(language))
25880
26221
  return [];
@@ -42307,6 +42648,9 @@ var JAVA_SET_HTTPONLY_TRUE_RE = /\.setHttpOnly\s*\(\s*true\s*\)/;
42307
42648
  var GO_SECURE_TRUE_RE = /\bSecure\s*:\s*true\b/;
42308
42649
  var GO_HTTPONLY_TRUE_RE = /\bHttpOnly\s*:\s*true\b/;
42309
42650
  var RUST_SET_COOKIE_MACRO_RE = /(format!|write!|writeln!)\s*\(([^()]*Set-Cookie[^()]*)\)/gis;
42651
+ var CS_COOKIE_OPTIONS_RE = /\bnew\s+CookieOptions\s*\{([^{}]*)\}/gs;
42652
+ var CS_SECURE_FALSE_RE = /\bSecure\s*=\s*false\b/;
42653
+ var CS_HTTPONLY_FALSE_RE = /\bHttpOnly\s*=\s*false\b/;
42310
42654
 
42311
42655
  class InsecureCookiePass {
42312
42656
  name = "insecure-cookie";
@@ -42355,9 +42699,30 @@ class InsecureCookiePass {
42355
42699
  insecureCookies.push(det);
42356
42700
  this.emit(ctx, file, det, "rust");
42357
42701
  }
42702
+ } else if (language === "csharp") {
42703
+ for (const det of this.detectCSharpCookieOptions(code)) {
42704
+ insecureCookies.push(det);
42705
+ this.emit(ctx, file, det, "csharp");
42706
+ }
42358
42707
  }
42359
42708
  return { insecureCookies };
42360
42709
  }
42710
+ detectCSharpCookieOptions(code) {
42711
+ const out2 = [];
42712
+ const re = new RegExp(CS_COOKIE_OPTIONS_RE.source, CS_COOKIE_OPTIONS_RE.flags);
42713
+ let m;
42714
+ while ((m = re.exec(code)) !== null) {
42715
+ const body2 = m[1] ?? "";
42716
+ const missingSecure = CS_SECURE_FALSE_RE.test(body2);
42717
+ const missingHttpOnly = CS_HTTPONLY_FALSE_RE.test(body2);
42718
+ if (!missingSecure && !missingHttpOnly)
42719
+ continue;
42720
+ const line = code.slice(0, m.index).split(`
42721
+ `).length;
42722
+ out2.push({ line, receiver: "CookieOptions", missingSecure, missingHttpOnly, optionsPresent: true });
42723
+ }
42724
+ return out2;
42725
+ }
42361
42726
  detectJs(call) {
42362
42727
  if (call.method_name !== "cookie")
42363
42728
  return null;
@@ -42471,12 +42836,12 @@ class InsecureCookiePass {
42471
42836
  emit(ctx, file, det, flavor) {
42472
42837
  const missing = [];
42473
42838
  if (det.missingSecure) {
42474
- missing.push(flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : "`Secure` attribute");
42839
+ missing.push(flavor === "js" ? "`secure: true`" : flavor === "python" ? "`secure=True`" : flavor === "java" ? "`setSecure(true)`" : flavor === "go" ? "`Secure: true`" : flavor === "csharp" ? "`Secure = true`" : "`Secure` attribute");
42475
42840
  }
42476
42841
  if (det.missingHttpOnly) {
42477
- missing.push(flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : "`HttpOnly` attribute");
42842
+ missing.push(flavor === "js" ? "`httpOnly: true`" : flavor === "python" ? "`httponly=True`" : flavor === "java" ? "`setHttpOnly(true)`" : flavor === "go" ? "`HttpOnly: true`" : flavor === "csharp" ? "`HttpOnly = true`" : "`HttpOnly` attribute");
42478
42843
  }
42479
- const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
42844
+ const fix = flavor === "js" ? 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the third argument to `res.cookie()`.' : flavor === "python" ? 'Pass `secure=True, httponly=True, samesite="Lax"` to `response.set_cookie(...)`.' : flavor === "java" ? "After constructing the cookie, call `cookie.setSecure(true)` and `cookie.setHttpOnly(true)` before adding it to the response." : flavor === "go" ? "Set `Secure: true` and `HttpOnly: true` on the `http.Cookie` struct literal passed to `http.SetCookie`." : flavor === "csharp" ? "Set `Secure = true` and `HttpOnly = true` on the `CookieOptions` (or enforce them globally via `CookiePolicyOptions`)." : "Append `; Secure; HttpOnly` to the `Set-Cookie` header string.";
42480
42845
  ctx.addFinding({
42481
42846
  id: `${this.name}-${file}-${det.line}`,
42482
42847
  pass: this.name,
@@ -42985,6 +43350,7 @@ var ISSUE_CWE = {
42985
43350
  "hardcoded-key": "CWE-321",
42986
43351
  "weak-rsa-key": "CWE-326"
42987
43352
  };
43353
+ var CS_CIPHER_MODE_ECB_RE = /\bCipherMode\s*\.\s*ECB\b/;
42988
43354
 
42989
43355
  class WeakCryptoPass {
42990
43356
  name = "weak-crypto";
@@ -43001,26 +43367,35 @@ class WeakCryptoPass {
43001
43367
  const findings = [];
43002
43368
  const constProp = ctx.hasResult("constant-propagation") ? ctx.getResult("constant-propagation") : null;
43003
43369
  const literalBindings = scanLiteralBindings(code, language);
43370
+ const emit = (line, det) => {
43371
+ findings.push({ line, language, ...det });
43372
+ ctx.addFinding({
43373
+ id: `${this.name}-${file}-${line}-${det.issue}`,
43374
+ pass: this.name,
43375
+ category: this.category,
43376
+ rule_id: this.name,
43377
+ cwe: ISSUE_CWE[det.issue],
43378
+ severity: "high",
43379
+ level: "error",
43380
+ message: this.buildMessage(det),
43381
+ file,
43382
+ line,
43383
+ fix: this.buildFix(det.issue),
43384
+ evidence: { ...det, language }
43385
+ });
43386
+ };
43004
43387
  for (const call of graph.ir.calls) {
43005
- const detections = this.detect(call, language, constProp, literalBindings);
43006
- for (const det of detections) {
43007
- const line = call.location.line;
43008
- findings.push({ line, language, ...det });
43009
- const message = this.buildMessage(det);
43010
- ctx.addFinding({
43011
- id: `${this.name}-${file}-${line}-${det.issue}`,
43012
- pass: this.name,
43013
- category: this.category,
43014
- rule_id: this.name,
43015
- cwe: ISSUE_CWE[det.issue],
43016
- severity: "high",
43017
- level: "error",
43018
- message,
43019
- file,
43020
- line,
43021
- fix: this.buildFix(det.issue),
43022
- evidence: { ...det, language }
43023
- });
43388
+ for (const det of this.detect(call, language, constProp, literalBindings)) {
43389
+ emit(call.location.line, det);
43390
+ }
43391
+ }
43392
+ if (language === "csharp") {
43393
+ const lines = code.split(`
43394
+ `);
43395
+ for (let i2 = 0;i2 < lines.length; i2++) {
43396
+ if (CS_CIPHER_MODE_ECB_RE.test(lines[i2])) {
43397
+ emit(i2 + 1, { issue: "ecb-mode", detail: "CipherMode.ECB", api: "SymmetricAlgorithm.Mode" });
43398
+ }
43024
43399
  }
43025
43400
  }
43026
43401
  return { findings };
@@ -44610,6 +44985,8 @@ var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
44610
44985
  var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
44611
44986
  var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
44612
44987
  var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
44988
+ var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
44989
+ var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
44613
44990
  var ALLOW_ALL_HOSTNAME_VERIFIERS = new Set([
44614
44991
  "NoopHostnameVerifier.INSTANCE",
44615
44992
  "new AllowAllHostnameVerifier()",
@@ -44758,6 +45135,21 @@ class TlsVerifyDisabledPass {
44758
45135
  }
44759
45136
  }
44760
45137
  }
45138
+ if (language === "csharp") {
45139
+ for (let i2 = 0;i2 < lines.length; i2++) {
45140
+ const l = lines[i2];
45141
+ const m = CS_CERT_CALLBACK_TRUE_RE.exec(l);
45142
+ if (m) {
45143
+ out2.push({ line: i2 + 1, pattern: `${m[1]} => true`, api: m[1] });
45144
+ } else if (CS_DANGEROUS_ACCEPT_RE.test(l)) {
45145
+ out2.push({
45146
+ line: i2 + 1,
45147
+ pattern: "DangerousAcceptAnyServerCertificateValidator",
45148
+ api: "HttpClientHandler"
45149
+ });
45150
+ }
45151
+ }
45152
+ }
44761
45153
  return out2;
44762
45154
  }
44763
45155
  fixFor(language, pattern) {
@@ -44779,6 +45171,9 @@ class TlsVerifyDisabledPass {
44779
45171
  if (pattern.includes("ssl._create_unverified_context")) {
44780
45172
  return "Do not use `_create_unverified_context()`. Use `ssl.create_default_context()`.";
44781
45173
  }
45174
+ if (language === "csharp") {
45175
+ return "Do not accept every certificate. Remove the always-true validation " + "callback (and `DangerousAcceptAnyServerCertificateValidator`); rely on the " + "platform default. To trust a private CA, validate the chain against it in " + "the callback instead of returning true.";
45176
+ }
44782
45177
  return "Restore TLS certificate and hostname verification.";
44783
45178
  }
44784
45179
  }
@@ -45306,6 +45701,8 @@ var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
45306
45701
  var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
45307
45702
  var PY_ALG_NONE_RE = /\balgorithms\s*=\s*[\[\(]\s*["']none["']/i;
45308
45703
  var JS_ALG_NONE_RE = /\balgorithms\s*:\s*\[\s*["']none["']/i;
45704
+ var CS_REQUIRE_SIGNED_FALSE_RE = /\bRequireSignedTokens\s*=\s*false\b/;
45705
+ var CS_SIGNATURE_VALIDATOR_BYPASS_RE = /\bSignatureValidator\s*=\s*[^;]*=>\s*new\s+JwtSecurityToken\b/;
45309
45706
 
45310
45707
  class JwtVerifyDisabledPass {
45311
45708
  name = "jwt-verify-disabled";
@@ -45314,25 +45711,36 @@ class JwtVerifyDisabledPass {
45314
45711
  const { graph, language } = ctx;
45315
45712
  const file = graph.ir.meta.file;
45316
45713
  const findings = [];
45714
+ const emit = (line, det) => {
45715
+ findings.push({ line, language, ...det });
45716
+ ctx.addFinding({
45717
+ id: `${this.name}-${file}-${line}-${det.pattern}`,
45718
+ pass: this.name,
45719
+ category: this.category,
45720
+ rule_id: this.name,
45721
+ cwe: "CWE-347",
45722
+ severity: "critical",
45723
+ level: "error",
45724
+ message: `JWT signature verification disabled via \`${det.pattern}\` in ` + `\`${det.api}\`. Any attacker can forge a token with arbitrary ` + "claims (user id, roles, expiry) since the signature is not " + "checked.",
45725
+ file,
45726
+ line,
45727
+ fix: this.fixFor(language),
45728
+ evidence: { ...det, language }
45729
+ });
45730
+ };
45317
45731
  for (const call of graph.ir.calls) {
45318
- const detections = this.detect(call, language);
45319
- for (const det of detections) {
45320
- const line = call.location.line;
45321
- findings.push({ line, language, ...det });
45322
- ctx.addFinding({
45323
- id: `${this.name}-${file}-${line}-${det.pattern}`,
45324
- pass: this.name,
45325
- category: this.category,
45326
- rule_id: this.name,
45327
- cwe: "CWE-347",
45328
- severity: "critical",
45329
- level: "error",
45330
- message: `JWT signature verification disabled via \`${det.pattern}\` in ` + `\`${det.api}\`. Any attacker can forge a token with arbitrary ` + "claims (user id, roles, expiry) since the signature is not " + "checked.",
45331
- file,
45332
- line,
45333
- fix: this.fixFor(language),
45334
- evidence: { ...det, language }
45335
- });
45732
+ for (const det of this.detect(call, language))
45733
+ emit(call.location.line, det);
45734
+ }
45735
+ if (language === "csharp") {
45736
+ const lines = ctx.code.split(`
45737
+ `);
45738
+ for (let i2 = 0;i2 < lines.length; i2++) {
45739
+ if (CS_REQUIRE_SIGNED_FALSE_RE.test(lines[i2])) {
45740
+ emit(i2 + 1, { pattern: "RequireSignedTokens = false", api: "TokenValidationParameters" });
45741
+ } else if (CS_SIGNATURE_VALIDATOR_BYPASS_RE.test(lines[i2])) {
45742
+ emit(i2 + 1, { pattern: "SignatureValidator returns an unvalidated token", api: "TokenValidationParameters" });
45743
+ }
45336
45744
  }
45337
45745
  }
45338
45746
  return { findings };
@@ -45406,6 +45814,9 @@ class JwtVerifyDisabledPass {
45406
45814
  if (language === "java") {
45407
45815
  return "For auth0/java-jwt: use `JWT.require(Algorithm.HMAC256(secret))` or " + "an RSA algorithm. For jjwt: call `parseClaimsJws(token)` (signature " + "enforced) rather than `parse(token)` (signature ignored).";
45408
45816
  }
45817
+ if (language === "csharp") {
45818
+ return "Leave `RequireSignedTokens = true` and do not install a custom " + "`SignatureValidator` that returns the token unverified. Configure " + "`IssuerSigningKey`/`IssuerSigningKeys` and let the handler validate " + "the signature.";
45819
+ }
45409
45820
  return "Enforce JWT signature verification with a concrete algorithm " + "(HS256/RS256/ES256). Never accept `alg: none`.";
45410
45821
  }
45411
45822
  }
@@ -48320,7 +48731,7 @@ var colors = {
48320
48731
  };
48321
48732
 
48322
48733
  // src/version.ts
48323
- var version = "4.8.2";
48734
+ var version = "4.9.8";
48324
48735
 
48325
48736
  // src/formatters.ts
48326
48737
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -48871,6 +49282,12 @@ function generateSarifResults(results, crossFileData) {
48871
49282
  return sarifResults;
48872
49283
  }
48873
49284
 
49285
+ // src/build-info.ts
49286
+ var buildInfo = {
49287
+ gitSha: "5e6bcc1",
49288
+ builtAt: "2026-08-25T19:50:02.936Z"
49289
+ };
49290
+
48874
49291
  // src/utils/args.ts
48875
49292
  function parseArgs(argv) {
48876
49293
  const args2 = [];
@@ -49011,8 +49428,11 @@ EXAMPLES:
49011
49428
  For more information, visit: https://cognium.dev
49012
49429
  `);
49013
49430
  }
49014
- function showVersion(version2) {
49431
+ function showVersion(version2, buildInfo2) {
49015
49432
  console.log(`cognium-dev v${version2}`);
49433
+ if (buildInfo2?.builtAt) {
49434
+ console.log(`build ${buildInfo2.gitSha} · ${buildInfo2.builtAt}`);
49435
+ }
49016
49436
  console.log(`Powered by Cognium Labs`);
49017
49437
  }
49018
49438
 
@@ -50146,7 +50566,7 @@ async function main() {
50146
50566
  return;
50147
50567
  }
50148
50568
  if (command === "version" || options.version || options.V) {
50149
- showVersion(version);
50569
+ showVersion(version, buildInfo);
50150
50570
  return;
50151
50571
  }
50152
50572
  if (command === "init") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.8.2",
3
+ "version": "4.9.8",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,13 +8,13 @@
8
8
  "cognium-dev": "dist/cli.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "bun build src/cli.ts --outdir dist --target node --format esm --external pino-pretty",
12
- "build:standalone": "mkdir -p wasm && cp ../circle-ir/wasm/*.wasm wasm/ && bun build src/cli.ts --compile --outfile cognium-dev --external pino-pretty",
11
+ "build": "node scripts/build.mjs",
12
+ "build:standalone": "mkdir -p wasm && cp ../circle-ir/wasm/*.wasm wasm/ && node scripts/build.mjs --standalone",
13
13
  "dev": "bun run src/cli.ts",
14
14
  "test": "bun test",
15
15
  "clean": "rm -rf dist cognium-dev cognium-dev-* *.bun-build wasm",
16
16
  "typecheck": "tsc --noEmit",
17
- "version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually use \\`npm version patch|minor|major\\` instead.\\n */\\nexport const version = \\x27' + v + '\\x27;\\n')\" && git add src/version.ts",
17
+ "version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually \u2014 use \\`npm version patch|minor|major\\` instead.\\n */\\nexport const version = \\x27' + v + '\\x27;\\n')\" && git add src/version.ts",
18
18
  "dogfood": "bun run src/cli.ts scan src/ -q",
19
19
  "prepublishOnly": "bun run build"
20
20
  },
@@ -65,8 +65,8 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "@cognium/project-profile-detect": "^1.1.0",
69
- "circle-ir": "^4.8.2"
68
+ "@cognium/project-profile-detect": "1.1.0",
69
+ "circle-ir": "4.9.8"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",