cognium-dev 3.54.0 → 3.55.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.
Files changed (2) hide show
  1. package/dist/cli.js +380 -11
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -10476,8 +10476,8 @@ var DEFAULT_SINKS = [
10476
10476
  { method: "print", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
10477
10477
  { method: "println", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
10478
10478
  { method: "sendError", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10479
- { method: "setHeader", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10480
- { method: "addHeader", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10479
+ { method: "setHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
10480
+ { method: "addHeader", class: "HttpServletResponse", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1] },
10481
10481
  { method: "setContentType", class: "HttpServletResponse", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
10482
10482
  { method: "setAttribute", class: "PageContext", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
10483
10483
  { method: "addAttribute", class: "Model", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
@@ -11163,7 +11163,18 @@ var DEFAULT_SINKS = [
11163
11163
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11164
11164
  { method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11165
11165
  { method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11166
- { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] }
11166
+ { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
11167
+ { method: "setHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
11168
+ { method: "writeHead", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [2], languages: ["javascript", "typescript"] },
11169
+ { method: "cookie", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
11170
+ { method: "location", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
11171
+ { method: "redirect", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
11172
+ { method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11173
+ { method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11174
+ { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11175
+ { method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11176
+ { method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11177
+ { method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] }
11167
11178
  ];
11168
11179
  var DEFAULT_SANITIZERS = [
11169
11180
  { method: "setString", class: "PreparedStatement", removes: ["sql_injection"] },
@@ -21503,7 +21514,9 @@ var KNOWN_SINK_TYPES = new Set([
21503
21514
  "code_injection",
21504
21515
  "mybatis_mapper_call",
21505
21516
  "redos",
21506
- "format_string"
21517
+ "format_string",
21518
+ "crlf",
21519
+ "mass_assignment"
21507
21520
  ]);
21508
21521
  function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine) {
21509
21522
  const sanitizersAtTarget = sanitizersByLine.get(toLine);
@@ -21558,19 +21571,19 @@ function buildTaintFlow(source, sink, taintInfo) {
21558
21571
  // ../circle-ir/dist/analysis/findings.js
21559
21572
  function canSourceReachSink(sourceType, sinkType) {
21560
21573
  const sourceToSinkMapping = {
21561
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection"],
21562
- http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call"],
21563
- http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection"],
21564
- http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection"],
21574
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
21575
+ http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
21576
+ http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf"],
21577
+ http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf"],
21565
21578
  http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call"],
21566
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection"],
21579
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
21567
21580
  io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss"],
21568
21581
  env_input: ["command_injection", "path_traversal"],
21569
21582
  db_input: ["xss", "sql_injection"],
21570
21583
  file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
21571
21584
  network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
21572
21585
  config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
21573
- interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call"],
21586
+ interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
21574
21587
  plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
21575
21588
  };
21576
21589
  const validSinks = sourceToSinkMapping[sourceType];
@@ -28154,6 +28167,356 @@ class JwtVerifyDisabledPass {
28154
28167
  }
28155
28168
  }
28156
28169
 
28170
+ // ../circle-ir/dist/analysis/passes/csrf-protection-disabled-pass.js
28171
+ var JAVA_CSRF_DISABLE_RE = /\.csrf\s*\([^)]*\)\s*\.\s*disable\b/;
28172
+ var JAVA_CSRF_LAMBDA_DISABLE_RE = /\bcsrf\s*\(\s*\w+\s*->\s*\w+\s*\.\s*disable\s*\(/;
28173
+ var JAVA_CSRF_METHODREF_RE = /\bcsrf\s*\(\s*[\w.]+::disable\s*\)/;
28174
+ var JAVA_CSRF_NULL_REPO_RE = /\.csrfTokenRepository\s*\(\s*null\s*\)/;
28175
+
28176
+ class CsrfProtectionDisabledPass {
28177
+ name = "csrf-protection-disabled";
28178
+ category = "security";
28179
+ run(ctx) {
28180
+ const { graph, language } = ctx;
28181
+ const file = graph.ir.meta.file;
28182
+ const findings = [];
28183
+ for (const call of graph.ir.calls) {
28184
+ const detections = this.detectCall(call, language);
28185
+ for (const det of detections) {
28186
+ const line = call.location.line;
28187
+ findings.push({ line, language, ...det });
28188
+ ctx.addFinding({
28189
+ id: `${this.name}-${file}-${line}-${det.pattern}`,
28190
+ pass: this.name,
28191
+ category: this.category,
28192
+ rule_id: this.name,
28193
+ cwe: "CWE-352",
28194
+ severity: "critical",
28195
+ level: "error",
28196
+ message: `CSRF protection explicitly disabled via \`${det.pattern}\` ` + `(${det.api}). Any browser session can be silently used to ` + "perform state-changing requests from a malicious origin.",
28197
+ file,
28198
+ line,
28199
+ fix: this.fixFor(language),
28200
+ evidence: { ...det, language }
28201
+ });
28202
+ }
28203
+ }
28204
+ if (language === "java") {
28205
+ const src = ctx.code ?? "";
28206
+ if (src) {
28207
+ const lines = src.split(`
28208
+ `);
28209
+ for (let i2 = 0;i2 < lines.length; i2++) {
28210
+ const line = i2 + 1;
28211
+ const text = lines[i2] ?? "";
28212
+ let det = null;
28213
+ if (JAVA_CSRF_LAMBDA_DISABLE_RE.test(text)) {
28214
+ det = { pattern: "csrf(c -> c.disable())", api: "HttpSecurity.csrf" };
28215
+ } else if (JAVA_CSRF_METHODREF_RE.test(text)) {
28216
+ det = { pattern: "csrf(::disable)", api: "HttpSecurity.csrf" };
28217
+ } else if (JAVA_CSRF_NULL_REPO_RE.test(text)) {
28218
+ det = { pattern: "csrfTokenRepository(null)", api: "HttpSecurity.csrfTokenRepository" };
28219
+ } else if (JAVA_CSRF_DISABLE_RE.test(text)) {
28220
+ det = { pattern: "csrf().disable()", api: "HttpSecurity.csrf" };
28221
+ }
28222
+ if (det && !findings.some((f) => f.line === line && f.pattern === det.pattern)) {
28223
+ findings.push({ line, language, ...det });
28224
+ ctx.addFinding({
28225
+ id: `${this.name}-${file}-${line}-${det.pattern}`,
28226
+ pass: this.name,
28227
+ category: this.category,
28228
+ rule_id: this.name,
28229
+ cwe: "CWE-352",
28230
+ severity: "critical",
28231
+ level: "error",
28232
+ message: `CSRF protection explicitly disabled via \`${det.pattern}\` ` + `(${det.api}). Any browser session can be silently used to ` + "perform state-changing requests from a malicious origin.",
28233
+ file,
28234
+ line,
28235
+ fix: this.fixFor(language),
28236
+ evidence: { ...det, language }
28237
+ });
28238
+ }
28239
+ }
28240
+ }
28241
+ }
28242
+ if (language === "python") {
28243
+ const src = ctx.code ?? "";
28244
+ if (src) {
28245
+ const lines = src.split(`
28246
+ `);
28247
+ for (let i2 = 0;i2 < lines.length; i2++) {
28248
+ const text = lines[i2] ?? "";
28249
+ if (/^\s*@csrf_exempt\b/.test(text)) {
28250
+ const line = i2 + 1;
28251
+ const det = { pattern: "@csrf_exempt", api: "django.views.decorators.csrf" };
28252
+ findings.push({ line, language, ...det });
28253
+ ctx.addFinding({
28254
+ id: `${this.name}-${file}-${line}-${det.pattern}`,
28255
+ pass: this.name,
28256
+ category: this.category,
28257
+ rule_id: this.name,
28258
+ cwe: "CWE-352",
28259
+ severity: "critical",
28260
+ level: "error",
28261
+ message: "Django view is decorated with `@csrf_exempt`, bypassing the " + "framework CSRF middleware for this endpoint. Any browser " + "session can be silently used to invoke this handler from " + "a malicious origin.",
28262
+ file,
28263
+ line,
28264
+ fix: this.fixFor(language),
28265
+ evidence: { ...det, language }
28266
+ });
28267
+ }
28268
+ }
28269
+ }
28270
+ }
28271
+ return { findings };
28272
+ }
28273
+ detectCall(call, language) {
28274
+ const out2 = [];
28275
+ if (language !== "java")
28276
+ return out2;
28277
+ if (call.method_name === "disable") {
28278
+ const recv = call.receiver ?? "";
28279
+ if (/\bcsrf\s*\(\s*\)\s*$/.test(recv) || recv.endsWith(".csrf()")) {
28280
+ out2.push({ pattern: "csrf().disable()", api: "HttpSecurity.csrf" });
28281
+ }
28282
+ }
28283
+ if (call.method_name === "csrfTokenRepository") {
28284
+ const arg = call.arguments.find((a) => a.position === 0);
28285
+ const expr = (arg?.expression ?? arg?.literal ?? "").trim();
28286
+ if (expr === "null") {
28287
+ out2.push({
28288
+ pattern: "csrfTokenRepository(null)",
28289
+ api: "HttpSecurity.csrfTokenRepository"
28290
+ });
28291
+ }
28292
+ }
28293
+ return out2;
28294
+ }
28295
+ fixFor(language) {
28296
+ if (language === "java") {
28297
+ return "Leave Spring Security CSRF protection enabled. If you need to " + "exempt a specific endpoint (e.g. webhook), use " + '`.csrf(c -> c.ignoringRequestMatchers("/webhook"))` rather than ' + "`.disable()`. For stateless APIs, prefer a per-request token over " + "disabling CSRF entirely.";
28298
+ }
28299
+ if (language === "python") {
28300
+ return "Remove `@csrf_exempt`. For stateless API endpoints, use Django REST " + "Framework with a token / session auth backend that does not rely on " + "cookies. For webhook receivers, verify a shared-secret signature " + "instead of disabling CSRF.";
28301
+ }
28302
+ return "Re-enable framework CSRF protection or replace with origin / token validation.";
28303
+ }
28304
+ }
28305
+
28306
+ // ../circle-ir/dist/analysis/passes/xml-entity-expansion-pass.js
28307
+ var JAVA_FACTORIES = new Set([
28308
+ "SAXParserFactory",
28309
+ "DocumentBuilderFactory",
28310
+ "XMLInputFactory",
28311
+ "SchemaFactory",
28312
+ "TransformerFactory"
28313
+ ]);
28314
+ var JAVA_SAFE_EVIDENCE_RE = /(disallow-doctype-decl|external-general-entities|external-parameter-entities|SUPPORT_DTD|ACCESS_EXTERNAL_DTD|ACCESS_EXTERNAL_SCHEMA|setXIncludeAware\s*\(\s*false\s*\)|setExpandEntityReferences\s*\(\s*false\s*\))/;
28315
+ var PY_LXML_PARSER_INSECURE_DEFAULT_RE = /\bresolve_entities\s*=\s*False\b/;
28316
+
28317
+ class XmlEntityExpansionPass {
28318
+ name = "xml-entity-expansion";
28319
+ category = "security";
28320
+ run(ctx) {
28321
+ const { graph, language } = ctx;
28322
+ const file = graph.ir.meta.file;
28323
+ const findings = [];
28324
+ const code = ctx.code ?? "";
28325
+ if (language === "java") {
28326
+ const safeInFile = JAVA_SAFE_EVIDENCE_RE.test(code);
28327
+ if (safeInFile)
28328
+ return { findings };
28329
+ for (const call of graph.ir.calls) {
28330
+ const det = this.detectJavaCall(call);
28331
+ if (!det)
28332
+ continue;
28333
+ const line = call.location.line;
28334
+ findings.push({ line, language, ...det });
28335
+ ctx.addFinding({
28336
+ id: `${this.name}-${file}-${line}-${det.api}`,
28337
+ pass: this.name,
28338
+ category: this.category,
28339
+ rule_id: this.name,
28340
+ cwe: det.cwe,
28341
+ severity: "high",
28342
+ level: "error",
28343
+ message: `${det.api} created without disabling DTD / external-entity ` + "processing. Vulnerable to billion-laughs / quadratic " + "blow-up DoS (CWE-776) and external-entity disclosure " + '(CWE-611). Add `setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` (or the equivalent) before ' + "parsing.",
28344
+ file,
28345
+ line,
28346
+ fix: this.fixForJava(det.api),
28347
+ evidence: { ...det, language, safeFeatureInFile: false }
28348
+ });
28349
+ }
28350
+ return { findings };
28351
+ }
28352
+ if (language === "python") {
28353
+ const safeInFile = PY_LXML_PARSER_INSECURE_DEFAULT_RE.test(code) || /\bdefusedxml\b/.test(code);
28354
+ if (safeInFile)
28355
+ return { findings };
28356
+ for (const call of graph.ir.calls) {
28357
+ const det = this.detectPythonCall(call);
28358
+ if (!det)
28359
+ continue;
28360
+ const line = call.location.line;
28361
+ findings.push({ line, language, ...det });
28362
+ ctx.addFinding({
28363
+ id: `${this.name}-${file}-${line}-${det.api}`,
28364
+ pass: this.name,
28365
+ category: this.category,
28366
+ rule_id: this.name,
28367
+ cwe: det.cwe,
28368
+ severity: "high",
28369
+ level: "error",
28370
+ message: `${det.api} called without an entity-safe parser. Vulnerable ` + "to billion-laughs / quadratic blow-up DoS (CWE-776) and " + "external-entity disclosure (CWE-611). Use `defusedxml` or pass " + "an `XMLParser(resolve_entities=False)` to lxml.",
28371
+ file,
28372
+ line,
28373
+ fix: this.fixForPython(det.api),
28374
+ evidence: { ...det, language, safeFeatureInFile: false }
28375
+ });
28376
+ }
28377
+ return { findings };
28378
+ }
28379
+ return { findings };
28380
+ }
28381
+ detectJavaCall(call) {
28382
+ if (call.method_name !== "newInstance")
28383
+ return null;
28384
+ const recv = call.receiver ?? "";
28385
+ const recvType = call.receiver_type ?? "";
28386
+ for (const factory of JAVA_FACTORIES) {
28387
+ if (recv === factory || recvType === factory || recv.endsWith("." + factory) || recvType.endsWith("." + factory)) {
28388
+ return {
28389
+ pattern: `${factory}.newInstance()`,
28390
+ api: factory,
28391
+ cwe: "CWE-776"
28392
+ };
28393
+ }
28394
+ }
28395
+ return null;
28396
+ }
28397
+ detectPythonCall(call) {
28398
+ const recv = call.receiver ?? "";
28399
+ const method = call.method_name;
28400
+ if ((method === "parse" || method === "fromstring" || method === "XML") && (recv === "etree" || recv.endsWith(".etree"))) {
28401
+ return {
28402
+ pattern: `etree.${method}`,
28403
+ api: `lxml.etree.${method}`,
28404
+ cwe: "CWE-776"
28405
+ };
28406
+ }
28407
+ if ((method === "parse" || method === "fromstring") && (recv === "ET" || recv === "ElementTree" || recv.endsWith(".ElementTree"))) {
28408
+ return {
28409
+ pattern: `ElementTree.${method}`,
28410
+ api: `xml.etree.ElementTree.${method}`,
28411
+ cwe: "CWE-776"
28412
+ };
28413
+ }
28414
+ return null;
28415
+ }
28416
+ fixForJava(api) {
28417
+ if (api === "SAXParserFactory") {
28418
+ return 'Call `factory.setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` and ' + "`factory.setXIncludeAware(false)` before `newSAXParser()`.";
28419
+ }
28420
+ if (api === "DocumentBuilderFactory") {
28421
+ return 'Call `factory.setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` and ' + "`factory.setExpandEntityReferences(false)` before " + "`newDocumentBuilder()`.";
28422
+ }
28423
+ if (api === "XMLInputFactory") {
28424
+ return "Call `factory.setProperty(XMLInputFactory.SUPPORT_DTD, false)` " + "and `factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_" + "ENTITIES, false)` before `createXMLStreamReader`.";
28425
+ }
28426
+ return "Use `XMLConstants.FEATURE_SECURE_PROCESSING` and explicitly disable " + "DTD / external-entity loading on the factory before parsing.";
28427
+ }
28428
+ fixForPython(api) {
28429
+ if (api.startsWith("lxml.etree")) {
28430
+ return "Pass an explicit parser: " + "`etree.parse(src, parser=etree.XMLParser(resolve_entities=False, " + "no_network=True))`. Even better, use the `defusedxml.lxml` wrapper.";
28431
+ }
28432
+ return "Replace `xml.etree.ElementTree` with `defusedxml.ElementTree`, which " + "disables DTD / entity processing by default.";
28433
+ }
28434
+ }
28435
+
28436
+ // ../circle-ir/dist/analysis/passes/mass-assignment-pass.js
28437
+ var PY_KWARGS_SPLAT_RE = /\*\*\s*(?:request|self\.request|flask\.request|ctx|self)\s*\.\s*(?:form|args|values|json|get_json\s*\(\s*\)|files|data)/;
28438
+ var JS_OBJECT_SPREAD_RE = /\{\s*\.\.\.\s*(?:req|request|ctx|context)(?:\.request)?\s*\.\s*(?:body|query|params|form)\b/;
28439
+
28440
+ class MassAssignmentPass {
28441
+ name = "mass-assignment";
28442
+ category = "security";
28443
+ run(ctx) {
28444
+ const { graph, language } = ctx;
28445
+ const file = graph.ir.meta.file;
28446
+ const findings = [];
28447
+ const code = ctx.code ?? "";
28448
+ if (!code)
28449
+ return { findings };
28450
+ const lines = code.split(`
28451
+ `);
28452
+ if (language === "python") {
28453
+ for (let i2 = 0;i2 < lines.length; i2++) {
28454
+ const text = lines[i2] ?? "";
28455
+ const m = PY_KWARGS_SPLAT_RE.exec(text);
28456
+ if (!m)
28457
+ continue;
28458
+ const line = i2 + 1;
28459
+ const det = {
28460
+ pattern: "**request.<bag>",
28461
+ match: m[0]
28462
+ };
28463
+ findings.push({
28464
+ line,
28465
+ language,
28466
+ pattern: det.pattern,
28467
+ snippet: text.trim().slice(0, 200)
28468
+ });
28469
+ ctx.addFinding({
28470
+ id: `${this.name}-${file}-${line}`,
28471
+ pass: this.name,
28472
+ category: this.category,
28473
+ rule_id: this.name,
28474
+ cwe: "CWE-915",
28475
+ severity: "high",
28476
+ level: "error",
28477
+ message: `HTTP request bag splatted into constructor / ORM helper via ` + `\`${det.match}\`. Every form field becomes a settable attribute ` + "on the domain object, including ones the endpoint did not " + "intend to expose (e.g. `is_admin`, `role`, `owner_id`).",
28478
+ file,
28479
+ line,
28480
+ fix: "Replace the `**` splat with an explicit allow-list: " + "`Model(name=request.form['name'], email=request.form['email'])`. " + "For Django, use a `ModelForm` / serializer with `fields = [...]`.",
28481
+ evidence: { pattern: det.pattern, match: det.match, language }
28482
+ });
28483
+ }
28484
+ return { findings };
28485
+ }
28486
+ if (language === "javascript" || language === "typescript") {
28487
+ for (let i2 = 0;i2 < lines.length; i2++) {
28488
+ const text = lines[i2] ?? "";
28489
+ const m = JS_OBJECT_SPREAD_RE.exec(text);
28490
+ if (!m)
28491
+ continue;
28492
+ const line = i2 + 1;
28493
+ findings.push({
28494
+ line,
28495
+ language,
28496
+ pattern: "{...req.<bag>}",
28497
+ snippet: text.trim().slice(0, 200)
28498
+ });
28499
+ ctx.addFinding({
28500
+ id: `${this.name}-${file}-${line}`,
28501
+ pass: this.name,
28502
+ category: this.category,
28503
+ rule_id: this.name,
28504
+ cwe: "CWE-915",
28505
+ severity: "high",
28506
+ level: "error",
28507
+ message: `HTTP request bag spread into object literal via \`${m[0]}\`. ` + "Every body field becomes a settable property on the resulting " + "object, including ones the endpoint did not intend to expose " + "(e.g. `isAdmin`, `role`, `ownerId`).",
28508
+ file,
28509
+ line,
28510
+ fix: "Replace the spread with an explicit pick: " + "`const { name, email } = req.body; const user = { name, email };`. " + "For ORMs, use a DTO / Zod schema with `.pick(...)` or " + "allow-list serializers.",
28511
+ evidence: { pattern: "{...req.<bag>}", match: m[0], language }
28512
+ });
28513
+ }
28514
+ return { findings };
28515
+ }
28516
+ return { findings };
28517
+ }
28518
+ }
28519
+
28157
28520
  // ../circle-ir/dist/graph/import-graph.js
28158
28521
  function dirname(filePath) {
28159
28522
  const idx = filePath.lastIndexOf("/");
@@ -29276,6 +29639,12 @@ async function analyze(code, filePath, language, options = {}) {
29276
29639
  pipeline.add(new TlsVerifyDisabledPass);
29277
29640
  if (!disabledPasses.has("jwt-verify-disabled"))
29278
29641
  pipeline.add(new JwtVerifyDisabledPass);
29642
+ if (!disabledPasses.has("csrf-protection-disabled"))
29643
+ pipeline.add(new CsrfProtectionDisabledPass);
29644
+ if (!disabledPasses.has("xml-entity-expansion"))
29645
+ pipeline.add(new XmlEntityExpansionPass);
29646
+ if (!disabledPasses.has("mass-assignment"))
29647
+ pipeline.add(new MassAssignmentPass);
29279
29648
  const { results, findings } = pipeline.run(graph, code, language, config);
29280
29649
  const sinkFilter = results.get("sink-filter");
29281
29650
  const interProc = results.get("interprocedural");
@@ -29469,7 +29838,7 @@ var colors = {
29469
29838
  };
29470
29839
 
29471
29840
  // src/version.ts
29472
- var version = "3.54.0";
29841
+ var version = "3.55.0";
29473
29842
 
29474
29843
  // src/formatters.ts
29475
29844
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.54.0",
3
+ "version": "3.55.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "circle-ir": "^3.54.0"
68
+ "circle-ir": "^3.55.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",