cognium-dev 3.45.0 → 3.47.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 +266 -2
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -3349,6 +3349,40 @@ async function parse(code, language) {
3349
3349
  }
3350
3350
  return tree;
3351
3351
  }
3352
+ function extractParseStatus(tree) {
3353
+ const root = tree.rootNode;
3354
+ if (!root.hasError) {
3355
+ return { success: true, has_errors: false, error_count: 0, error_locations: [] };
3356
+ }
3357
+ const MAX_LOCATIONS = 50;
3358
+ const locations = [];
3359
+ let errorCount = 0;
3360
+ const stack = [root];
3361
+ while (stack.length > 0) {
3362
+ const node = stack.pop();
3363
+ if (node.type === "ERROR" || node.isMissing) {
3364
+ errorCount++;
3365
+ if (locations.length < MAX_LOCATIONS) {
3366
+ locations.push({
3367
+ line: node.startPosition.row + 1,
3368
+ column: node.startPosition.column
3369
+ });
3370
+ }
3371
+ }
3372
+ for (let i2 = 0;i2 < node.childCount; i2++) {
3373
+ const child = node.child(i2);
3374
+ if (child && (child.hasError || child.isMissing)) {
3375
+ stack.push(child);
3376
+ }
3377
+ }
3378
+ }
3379
+ return {
3380
+ success: false,
3381
+ has_errors: true,
3382
+ error_count: errorCount,
3383
+ error_locations: locations
3384
+ };
3385
+ }
3352
3386
  function disposeTree(tree) {
3353
3387
  if (!tree)
3354
3388
  return;
@@ -26513,6 +26547,214 @@ class ScanSecretsPass {
26513
26547
  }
26514
26548
  }
26515
26549
 
26550
+ // ../circle-ir/dist/analysis/passes/spring4shell-pass.js
26551
+ var CONTROLLER_ANNOTATIONS = new Set([
26552
+ "Controller",
26553
+ "RestController",
26554
+ "ControllerAdvice",
26555
+ "RestControllerAdvice"
26556
+ ]);
26557
+ var ROUTE_ANNOTATIONS = new Set([
26558
+ "RequestMapping",
26559
+ "GetMapping",
26560
+ "PostMapping",
26561
+ "PutMapping",
26562
+ "DeleteMapping",
26563
+ "PatchMapping"
26564
+ ]);
26565
+ var BINDING_ANNOTATIONS = new Set([
26566
+ "RequestBody",
26567
+ "RequestParam",
26568
+ "PathVariable",
26569
+ "RequestHeader",
26570
+ "CookieValue",
26571
+ "MatrixVariable",
26572
+ "ModelAttribute",
26573
+ "Valid",
26574
+ "Validated",
26575
+ "RequestPart",
26576
+ "SessionAttribute",
26577
+ "RequestAttribute"
26578
+ ]);
26579
+ var FRAMEWORK_PARAM_TYPES = new Set([
26580
+ "HttpServletRequest",
26581
+ "HttpServletResponse",
26582
+ "ServletRequest",
26583
+ "ServletResponse",
26584
+ "HttpSession",
26585
+ "ServletContext",
26586
+ "Cookie",
26587
+ "Model",
26588
+ "ModelMap",
26589
+ "ModelAndView",
26590
+ "Map",
26591
+ "BindingResult",
26592
+ "Errors",
26593
+ "RedirectAttributes",
26594
+ "SessionStatus",
26595
+ "WebRequest",
26596
+ "NativeWebRequest",
26597
+ "ServletWebRequest",
26598
+ "UriComponentsBuilder",
26599
+ "UriBuilder",
26600
+ "HttpEntity",
26601
+ "RequestEntity",
26602
+ "ResponseEntity",
26603
+ "HttpHeaders",
26604
+ "InputStream",
26605
+ "OutputStream",
26606
+ "Reader",
26607
+ "Writer",
26608
+ "ServerHttpRequest",
26609
+ "ServerHttpResponse",
26610
+ "ServerWebExchange",
26611
+ "Principal",
26612
+ "Authentication",
26613
+ "Locale",
26614
+ "TimeZone",
26615
+ "ZoneId",
26616
+ "MultipartFile",
26617
+ "Part",
26618
+ "TimeZone"
26619
+ ]);
26620
+ var SIMPLE_JAVA_TYPES = new Set([
26621
+ "boolean",
26622
+ "byte",
26623
+ "char",
26624
+ "short",
26625
+ "int",
26626
+ "long",
26627
+ "float",
26628
+ "double",
26629
+ "void",
26630
+ "Boolean",
26631
+ "Byte",
26632
+ "Character",
26633
+ "Short",
26634
+ "Integer",
26635
+ "Long",
26636
+ "Float",
26637
+ "Double",
26638
+ "String",
26639
+ "CharSequence",
26640
+ "BigInteger",
26641
+ "BigDecimal",
26642
+ "UUID",
26643
+ "Date",
26644
+ "Calendar",
26645
+ "Instant",
26646
+ "LocalDate",
26647
+ "LocalTime",
26648
+ "LocalDateTime",
26649
+ "OffsetDateTime",
26650
+ "OffsetTime",
26651
+ "ZonedDateTime",
26652
+ "Duration",
26653
+ "Period",
26654
+ "List",
26655
+ "Set",
26656
+ "Collection",
26657
+ "Iterable",
26658
+ "Optional"
26659
+ ]);
26660
+
26661
+ class Spring4ShellPass {
26662
+ name = "spring4shell";
26663
+ category = "security";
26664
+ run(ctx) {
26665
+ const { graph, language } = ctx;
26666
+ if (language !== "java") {
26667
+ return { controllerMethodsScanned: 0, findingsEmitted: 0 };
26668
+ }
26669
+ const file = graph.ir.meta.file;
26670
+ let scanned = 0;
26671
+ let emitted = 0;
26672
+ for (const type of graph.ir.types) {
26673
+ if (!isController(type))
26674
+ continue;
26675
+ for (const method of type.methods) {
26676
+ if (!isRouteHandler(method))
26677
+ continue;
26678
+ scanned++;
26679
+ for (const param of method.parameters) {
26680
+ if (!isVulnerableParameter(param))
26681
+ continue;
26682
+ ctx.addFinding({
26683
+ id: `${this.name}-${file}-${method.start_line}-${param.name}`,
26684
+ pass: this.name,
26685
+ category: this.category,
26686
+ rule_id: this.name,
26687
+ cwe: "CWE-94",
26688
+ severity: "high",
26689
+ level: "error",
26690
+ message: `Spring MVC controller method '${type.name}.${method.name}' binds parameter '${param.name}' of type '${param.type ?? "?"}' via implicit form-data binding (no @RequestBody / @RequestParam / @ModelAttribute) — vulnerable to Spring4Shell (CVE-2022-22965) class-graph RCE on Spring < 5.3.18 / 5.2.20`,
26691
+ file,
26692
+ line: param.line ?? method.start_line,
26693
+ fix: "Annotate the parameter with @RequestBody (JSON) or @ModelAttribute + @InitBinder/setAllowedFields whitelisting, upgrade Spring to ≥ 5.3.18 / 5.2.20, and ensure JDK is patched.",
26694
+ evidence: {
26695
+ controller_class: type.name,
26696
+ controller_annotations: type.annotations,
26697
+ method: method.name,
26698
+ method_annotations: method.annotations,
26699
+ parameter_name: param.name,
26700
+ parameter_type: param.type
26701
+ }
26702
+ });
26703
+ emitted++;
26704
+ }
26705
+ }
26706
+ }
26707
+ return { controllerMethodsScanned: scanned, findingsEmitted: emitted };
26708
+ }
26709
+ }
26710
+ function annotationHead(annotation) {
26711
+ const parenIdx = annotation.indexOf("(");
26712
+ return parenIdx >= 0 ? annotation.slice(0, parenIdx) : annotation;
26713
+ }
26714
+ function hasAnnotation(annotations, names) {
26715
+ for (const a of annotations) {
26716
+ if (names.has(annotationHead(a)))
26717
+ return true;
26718
+ }
26719
+ return false;
26720
+ }
26721
+ function isController(type) {
26722
+ return hasAnnotation(type.annotations, CONTROLLER_ANNOTATIONS);
26723
+ }
26724
+ function isRouteHandler(method) {
26725
+ return hasAnnotation(method.annotations, ROUTE_ANNOTATIONS);
26726
+ }
26727
+ function isVulnerableParameter(param) {
26728
+ if (hasAnnotation(param.annotations, BINDING_ANNOTATIONS))
26729
+ return false;
26730
+ if (!param.type)
26731
+ return false;
26732
+ const type = stripGenerics2(param.type).trim();
26733
+ if (!type)
26734
+ return false;
26735
+ if (type.endsWith("[]")) {
26736
+ const elem = type.slice(0, -2).trim();
26737
+ return !SIMPLE_JAVA_TYPES.has(elem) && isPotentialPojo(elem);
26738
+ }
26739
+ if (SIMPLE_JAVA_TYPES.has(type))
26740
+ return false;
26741
+ if (FRAMEWORK_PARAM_TYPES.has(type))
26742
+ return false;
26743
+ if (!isPotentialPojo(type))
26744
+ return false;
26745
+ return true;
26746
+ }
26747
+ function stripGenerics2(type) {
26748
+ const ltIdx = type.indexOf("<");
26749
+ return ltIdx >= 0 ? type.slice(0, ltIdx) : type;
26750
+ }
26751
+ function isPotentialPojo(type) {
26752
+ if (type.length === 0)
26753
+ return false;
26754
+ const first = type.charCodeAt(0);
26755
+ return first >= 65 && first <= 90;
26756
+ }
26757
+
26516
26758
  // ../circle-ir/dist/graph/import-graph.js
26517
26759
  function dirname(filePath) {
26518
26760
  const idx = filePath.lastIndexOf("/");
@@ -27509,6 +27751,15 @@ async function analyze(code, filePath, language, options = {}) {
27509
27751
  const tree = await parse(code, language);
27510
27752
  try {
27511
27753
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
27754
+ const parseStatus = extractParseStatus(tree);
27755
+ if (parseStatus.has_errors) {
27756
+ logger.warn("Partial parse — IR may be incomplete", {
27757
+ filePath,
27758
+ language,
27759
+ errorCount: parseStatus.error_count,
27760
+ firstErrorLine: parseStatus.error_locations[0]?.line
27761
+ });
27762
+ }
27512
27763
  const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
27513
27764
  const meta = extractMeta(code, tree, filePath, language);
27514
27765
  const types = extractTypes(tree, nodeCache, language);
@@ -27612,6 +27863,8 @@ async function analyze(code, filePath, language, options = {}) {
27612
27863
  pipeline.add(new NamingConventionPass(passOpts.namingConvention));
27613
27864
  if (!disabledPasses.has("security-headers"))
27614
27865
  pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
27866
+ if (!disabledPasses.has("spring4shell"))
27867
+ pipeline.add(new Spring4ShellPass);
27615
27868
  const { results, findings } = pipeline.run(graph, code, language, config);
27616
27869
  const sinkFilter = results.get("sink-filter");
27617
27870
  const interProc = results.get("interprocedural");
@@ -27645,7 +27898,8 @@ async function analyze(code, filePath, language, options = {}) {
27645
27898
  enriched,
27646
27899
  findings: findings.length > 0 ? findings : undefined,
27647
27900
  metrics: { file: filePath, metrics: metricValues },
27648
- runtime_registrations: runtimeRegistrations.length > 0 ? runtimeRegistrations : undefined
27901
+ runtime_registrations: runtimeRegistrations.length > 0 ? runtimeRegistrations : undefined,
27902
+ parse_status: parseStatus
27649
27903
  };
27650
27904
  } finally {
27651
27905
  disposeTree(tree);
@@ -27656,6 +27910,15 @@ async function analyzeHtmlFile(code, filePath, options) {
27656
27910
  const tree = await parse(code, "html");
27657
27911
  try {
27658
27912
  const meta = extractMeta(code, tree, filePath, "html");
27913
+ const htmlParseStatus = extractParseStatus(tree);
27914
+ if (htmlParseStatus.has_errors) {
27915
+ logger.warn("Partial parse — IR may be incomplete", {
27916
+ filePath,
27917
+ language: "html",
27918
+ errorCount: htmlParseStatus.error_count,
27919
+ firstErrorLine: htmlParseStatus.error_locations[0]?.line
27920
+ });
27921
+ }
27659
27922
  const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
27660
27923
  logger.debug("HTML extraction", {
27661
27924
  filePath,
@@ -27695,6 +27958,7 @@ async function analyzeHtmlFile(code, filePath, options) {
27695
27958
  }
27696
27959
  const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
27697
27960
  const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
27961
+ result.parse_status = htmlParseStatus;
27698
27962
  logger.debug("HTML analysis complete", {
27699
27963
  filePath,
27700
27964
  scriptBlocks: scriptResults.length,
@@ -27794,7 +28058,7 @@ var colors = {
27794
28058
  };
27795
28059
 
27796
28060
  // src/version.ts
27797
- var version = "3.45.0";
28061
+ var version = "3.47.0";
27798
28062
 
27799
28063
  // src/formatters.ts
27800
28064
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.45.0",
3
+ "version": "3.47.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.45.0"
68
+ "circle-ir": "^3.47.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",