regscale-cli 6.25.0.1__py3-none-any.whl → 6.26.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (84) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/application.py +18 -3
  4. regscale/core/app/internal/login.py +0 -1
  5. regscale/core/app/utils/catalog_utils/common.py +1 -1
  6. regscale/integrations/commercial/sicura/api.py +14 -13
  7. regscale/integrations/commercial/sicura/commands.py +8 -2
  8. regscale/integrations/commercial/sicura/scanner.py +49 -39
  9. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  10. regscale/integrations/commercial/synqly/assets.py +17 -0
  11. regscale/integrations/commercial/wizv2/click.py +26 -26
  12. regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
  13. regscale/integrations/commercial/wizv2/constants.py +20 -71
  14. regscale/integrations/commercial/wizv2/scanner.py +3 -3
  15. regscale/integrations/compliance_integration.py +67 -2
  16. regscale/integrations/control_matcher.py +358 -0
  17. regscale/integrations/due_date_handler.py +118 -6
  18. regscale/integrations/milestone_manager.py +291 -0
  19. regscale/integrations/public/__init__.py +1 -0
  20. regscale/integrations/public/cci_importer.py +37 -38
  21. regscale/integrations/public/fedramp/click.py +60 -2
  22. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  23. regscale/integrations/scanner_integration.py +199 -130
  24. regscale/models/integration_models/cisa_kev_data.json +199 -4
  25. regscale/models/integration_models/nexpose.py +36 -10
  26. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  27. regscale/models/locking.py +12 -8
  28. regscale/models/platform.py +1 -2
  29. regscale/models/regscale_models/control_implementation.py +46 -21
  30. regscale/models/regscale_models/issue.py +256 -94
  31. regscale/models/regscale_models/milestone.py +1 -1
  32. regscale/models/regscale_models/regscale_model.py +6 -1
  33. regscale/templates/__init__.py +0 -0
  34. regscale/utils/threading/threadhandler.py +20 -15
  35. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
  36. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
  37. tests/regscale/integrations/commercial/__init__.py +0 -0
  38. tests/regscale/integrations/commercial/conftest.py +28 -0
  39. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  40. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  41. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  42. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  43. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  44. tests/regscale/integrations/commercial/test_aws.py +3731 -0
  45. tests/regscale/integrations/commercial/test_burp.py +48 -0
  46. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  47. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  48. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  49. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  50. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  51. tests/regscale/integrations/commercial/test_jira.py +1814 -0
  52. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  53. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  54. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  55. tests/regscale/integrations/commercial/test_sicura.py +350 -0
  56. tests/regscale/integrations/commercial/test_snow.py +423 -0
  57. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  58. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  59. tests/regscale/integrations/commercial/test_stig.py +33 -0
  60. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  61. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  62. tests/regscale/integrations/commercial/test_wiz.py +1469 -0
  63. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  64. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  65. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  66. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  67. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  68. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
  69. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  70. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  71. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
  72. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  73. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
  74. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
  75. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  76. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  77. tests/regscale/integrations/test_control_matcher.py +1314 -0
  78. tests/regscale/integrations/test_control_matching.py +155 -0
  79. tests/regscale/integrations/test_milestone_manager.py +408 -0
  80. tests/regscale/models/test_issue.py +378 -1
  81. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
  82. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
  83. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
  84. {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
@@ -17,8 +17,10 @@ from regscale.integrations.commercial.wizv2.reports import WizReportManager
17
17
  from regscale.integrations.commercial.wizv2.variables import WizVariables
18
18
  from regscale.integrations.commercial.wizv2.wiz_auth import wiz_authenticate
19
19
  from regscale.integrations.compliance_integration import ComplianceIntegration, ComplianceItem
20
+ from regscale.integrations.control_matcher import ControlMatcher
20
21
  from regscale.models import regscale_models
21
- from regscale.models.regscale_models.control_implementation import ControlImplementation, ControlImplementationStatus
22
+ from regscale.models.regscale_models.control_implementation import ControlImplementation
23
+ from regscale.models.regscale_models.issue import IssueIdentification
22
24
 
23
25
  logger = logging.getLogger("regscale")
24
26
 
@@ -135,7 +137,12 @@ class WizComplianceReportItem(ComplianceItem):
135
137
  def _format_control_id(self, base_control: str, enhancement: str) -> str:
136
138
  """Format control ID with optional enhancement."""
137
139
  if enhancement:
138
- return f"{base_control}({enhancement})"
140
+ # Normalize enhancement number to remove leading zeros
141
+ try:
142
+ normalized_enhancement = str(int(enhancement))
143
+ except ValueError:
144
+ normalized_enhancement = enhancement
145
+ return f"{base_control}({normalized_enhancement})"
139
146
  else:
140
147
  return base_control
141
148
 
@@ -277,6 +284,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
277
284
 
278
285
  self.report_manager = WizReportManager(WizVariables.wizUrl, access_token)
279
286
 
287
+ # Initialize control matcher for robust control ID matching (inherited from parent but ensure it's set)
288
+ if not hasattr(self, "_control_matcher"):
289
+ self._control_matcher = ControlMatcher()
290
+
280
291
  def parse_csv_report(self, file_path: str) -> List[WizComplianceReportItem]:
281
292
  """
282
293
  Parse CSV compliance report.
@@ -783,7 +794,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
783
794
  """
784
795
  try:
785
796
  # Filter for compliance reports for this specific project
786
- filter_by = {"project": [self.wiz_project_id], "type": ["COMPLIANCE_ASSESSMENTS"]}
797
+ filter_by = {"projectId": [self.wiz_project_id], "type": ["COMPLIANCE_ASSESSMENTS"]}
787
798
 
788
799
  logger.debug(f"Searching for existing compliance reports with filter: {filter_by}")
789
800
  reports = self.report_manager.list_reports(filter_by=filter_by)
@@ -876,76 +887,66 @@ class WizComplianceReportProcessor(ComplianceIntegration):
876
887
  """
877
888
  Update passing controls to 'Implemented' status in RegScale.
878
889
 
890
+ Uses ControlMatcher for robust control ID matching with leading zero normalization.
891
+
879
892
  :param list[str] passing_control_ids: List of control IDs that passed
880
893
  """
881
894
  if not passing_control_ids:
882
895
  return
883
896
 
884
- # Initialize Application for control implementation updates
885
- # app = Application() # Will be used through self.app
886
-
887
897
  try:
888
- # Use the existing method that works for getting control name to implementation ID mapping
889
- control_impl_map = ControlImplementation.get_control_label_map_by_parent(
890
- parent_id=self.plan_id, parent_module=self.parent_module
891
- )
892
-
893
- logger.debug(
894
- f"Built control implementation map with {len(control_impl_map)} entries using get_control_label_map_by_parent"
895
- )
896
- if control_impl_map:
897
- sample_keys = list(control_impl_map.keys())[:10]
898
- logger.debug(f"Sample control names in map: {sample_keys}")
899
-
900
898
  logger.debug(f"Looking for passing control IDs: {passing_control_ids}")
901
899
 
902
900
  # Prepare batch updates for passing controls
903
901
  implementations_to_update = []
904
-
905
- # Debug: Show what keys are actually in the control_impl_map
906
- if control_impl_map:
907
- logger.debug(f"Control implementation map keys: {list(control_impl_map.keys())[:20]}")
902
+ controls_not_found = []
908
903
 
909
904
  for control_id in passing_control_ids:
910
- control_id_lower = control_id.lower()
911
- logger.debug(f"Looking for control '{control_id_lower}' in implementation map")
912
-
913
- if control_id_lower in control_impl_map:
914
- impl_id = control_impl_map[control_id_lower]
915
- logger.debug(f"Found matching implementation for '{control_id_lower}': {impl_id}")
916
-
917
- # Get the ControlImplementation object
918
- impl = ControlImplementation.get_object(object_id=impl_id)
919
- if impl:
920
- # Update status using compliance settings
921
- new_status = self._get_implementation_status_from_result("Pass")
922
- logger.debug(f"Setting control {control_id} status from 'Pass' result to: {new_status}")
923
- impl.status = new_status
924
- impl.dateLastAssessed = get_current_datetime()
925
- impl.lastAssessmentResult = "Pass"
926
- impl.bStatusImplemented = True
927
-
928
- # Ensure required fields are set if empty
929
- if not impl.responsibility:
930
- impl.responsibility = ControlImplementation.get_default_responsibility(
931
- parent_id=impl.parentId
932
- )
933
- logger.debug(
934
- f"Setting default responsibility for control {control_id}: {impl.responsibility}"
935
- )
936
-
937
- if not impl.implementation:
938
- impl.implementation = f"Implementation details for {control_id} will be documented."
939
- logger.debug(f"Setting default implementation statement for control {control_id}")
940
-
941
- # Set audit fields if available
942
- user_id = self.app.config.get("userId")
943
- if user_id:
944
- impl.lastUpdatedById = user_id
945
- impl.dateLastUpdated = get_current_datetime()
946
-
947
- implementations_to_update.append(impl.dict())
948
- logger.info(f"Marking control {control_id} as {new_status}")
905
+ # Use ControlMatcher to find implementation with robust control ID matching
906
+ impl = self._control_matcher.find_control_implementation(
907
+ control_id=control_id, parent_id=self.plan_id, parent_module=self.parent_module
908
+ )
909
+
910
+ if impl:
911
+ logger.debug(f"Found matching implementation for '{control_id}': {impl.id}")
912
+
913
+ # Update status using compliance settings
914
+ new_status = self._get_implementation_status_from_result("Pass")
915
+ logger.debug(f"Setting control {control_id} status from 'Pass' result to: {new_status}")
916
+ impl.status = new_status
917
+ impl.dateLastAssessed = get_current_datetime()
918
+ impl.lastAssessmentResult = "Pass"
919
+ impl.bStatusImplemented = True
920
+
921
+ # Ensure required fields are set if empty
922
+ if not impl.responsibility:
923
+ impl.responsibility = ControlImplementation.get_default_responsibility(parent_id=impl.parentId)
924
+ logger.debug(f"Setting default responsibility for control {control_id}: {impl.responsibility}")
925
+
926
+ if not impl.implementation:
927
+ impl.implementation = f"Implementation details for {control_id} will be documented."
928
+ logger.debug(f"Setting default implementation statement for control {control_id}")
929
+
930
+ # Set audit fields if available
931
+ user_id = self.app.config.get("userId")
932
+ if user_id:
933
+ impl.lastUpdatedById = user_id
934
+ impl.dateLastUpdated = get_current_datetime()
935
+
936
+ implementations_to_update.append(impl.dict())
937
+ logger.info(f"Marking control {control_id} as {new_status}")
938
+ else:
939
+ logger.debug(f"Control '{control_id}' not found in implementation map")
940
+ controls_not_found.append(control_id)
941
+
942
+ # Log summary
943
+ if controls_not_found:
944
+ logger.info(f"Passing control IDs not found in plan: {', '.join(sorted(controls_not_found))}")
945
+
946
+ logger.info(
947
+ f"Control implementation status update summary: {len(implementations_to_update)} found, "
948
+ f"{len(controls_not_found)} not in plan"
949
+ )
949
950
 
950
951
  # Batch update all implementations
951
952
  if implementations_to_update:
@@ -957,104 +958,47 @@ class WizComplianceReportProcessor(ComplianceIntegration):
957
958
  except Exception as e:
958
959
  logger.error(f"Error updating control implementation status: {e}")
959
960
 
960
- def _update_failing_controls_to_in_remediation(self, control_ids: List[str]) -> None:
961
- """
962
- Update control implementation status to In Remediation for failing controls.
963
-
964
- :param List[str] control_ids: List of control IDs that are failing
965
- :return: None
966
- :rtype: None
961
+ def _prepare_failing_control_update(self, control_id: str) -> Optional[dict]:
967
962
  """
968
- if not control_ids:
969
- return
970
-
971
- try:
972
- control_impl_map = self._get_control_implementation_map()
973
- if not control_impl_map:
974
- return
975
-
976
- implementations_to_update, controls_not_found = self._process_failing_control_ids(
977
- control_ids, control_impl_map
978
- )
979
-
980
- self._log_update_summary(implementations_to_update, controls_not_found)
981
- self._batch_update_implementations(implementations_to_update)
982
-
983
- except Exception as e:
984
- logger.error(f"Error updating failing control implementation status: {e}")
985
-
986
- def _get_control_implementation_map(self) -> dict:
987
- """Get control implementation map and validate it exists."""
988
-
989
- control_impl_map = ControlImplementation.get_control_label_map_by_parent(
990
- parent_id=self.plan_id, parent_module=self.parent_module
991
- )
992
-
993
- if not control_impl_map:
994
- logger.warning("No control implementation mapping found for security plan")
995
- return {}
963
+ Prepare a single failing control for update.
996
964
 
997
- logger.debug(f"Control implementation map contains {len(control_impl_map)} entries")
998
- return control_impl_map
999
-
1000
- def _process_failing_control_ids(self, control_ids: List[str], control_impl_map: dict) -> tuple[list, list]:
1001
- """Process failing control IDs and return implementations to update and controls not found."""
1002
-
1003
- logger.debug(f"Looking for failing control IDs: {control_ids}")
1004
- implementations_to_update = []
1005
- controls_not_found = []
1006
-
1007
- # Debug: Show what keys are actually in the control_impl_map for comparison
1008
- if control_impl_map:
1009
- logger.debug(f"Control implementation map keys (first 20): {list(control_impl_map.keys())[:20]}")
1010
-
1011
- for control_id in control_ids:
1012
- control_id_normalized = control_id.lower()
1013
- logger.debug(f"Looking for control '{control_id_normalized}' in implementation map")
1014
-
1015
- if control_id_normalized in control_impl_map:
1016
- impl = self._update_single_control_implementation(
1017
- control_id, control_id_normalized, control_impl_map[control_id_normalized]
1018
- )
1019
- if impl:
1020
- implementations_to_update.append(impl)
1021
- else:
1022
- controls_not_found.append(control_id)
1023
- else:
1024
- logger.debug(f"Control '{control_id_normalized}' not found in implementation map")
1025
- controls_not_found.append(control_id)
1026
-
1027
- return implementations_to_update, controls_not_found
1028
-
1029
- def _update_single_control_implementation(
1030
- self, control_id: str, control_id_normalized: str, impl_id: int
1031
- ) -> Optional[dict]:
1032
- """Update a single control implementation to In Remediation status.
1033
- :param str control_id: ID of the control to update
1034
- :param str control_id_normalized: ID of the control to update
1035
- :param int impl_id: ID of the implementation to update
1036
- :return: Updated implementation status if implementation exists
965
+ :param str control_id: Control ID to update
966
+ :return: Dictionary representation of updated implementation, or None if not found
1037
967
  :rtype: Optional[dict]
1038
968
  """
1039
- from regscale.core.app.utils.app_utils import get_current_datetime
1040
- from regscale.models.regscale_models import ControlImplementationStatus
1041
-
1042
- logger.debug(f"Found matching implementation for '{control_id_normalized}': {impl_id}")
969
+ impl = self._control_matcher.find_control_implementation(
970
+ control_id=control_id, parent_id=self.plan_id, parent_module=self.parent_module
971
+ )
1043
972
 
1044
- impl = ControlImplementation.get_object(object_id=impl_id)
1045
973
  if not impl:
1046
- logger.warning(f"Could not retrieve implementation object for ID {impl_id}")
974
+ logger.debug(f"Control '{control_id}' not found in implementation map")
1047
975
  return None
1048
976
 
1049
- # Update status using compliance settings
977
+ logger.debug(f"Found matching implementation for '{control_id}': {impl.id}")
978
+
1050
979
  new_status = self._get_implementation_status_from_result("Fail")
1051
980
  logger.debug(f"Setting control {control_id} status from 'Fail' result to: {new_status}")
981
+
1052
982
  impl.status = new_status
1053
983
  impl.dateLastAssessed = get_current_datetime()
1054
984
  impl.lastAssessmentResult = "Fail"
1055
985
  impl.bStatusImplemented = False
1056
986
 
1057
- # Ensure required fields are set if empty
987
+ self._set_default_fields_if_empty(impl, control_id)
988
+ self._set_audit_fields(impl)
989
+
990
+ logger.info(f"Marking control {control_id} as {new_status}")
991
+ return impl.dict()
992
+
993
+ def _set_default_fields_if_empty(self, impl: ControlImplementation, control_id: str) -> None:
994
+ """
995
+ Set default values for required fields if they are empty.
996
+
997
+ :param ControlImplementation impl: Implementation to update
998
+ :param str control_id: Control ID for logging
999
+ :return: None
1000
+ :rtype: None
1001
+ """
1058
1002
  if not impl.responsibility:
1059
1003
  impl.responsibility = ControlImplementation.get_default_responsibility(parent_id=impl.parentId)
1060
1004
  logger.debug(f"Setting default responsibility for control {control_id}: {impl.responsibility}")
@@ -1063,27 +1007,76 @@ class WizComplianceReportProcessor(ComplianceIntegration):
1063
1007
  impl.implementation = f"Implementation details for {control_id} will be documented."
1064
1008
  logger.debug(f"Setting default implementation statement for control {control_id}")
1065
1009
 
1066
- # Set audit fields if available
1010
+ def _set_audit_fields(self, impl: ControlImplementation) -> None:
1011
+ """
1012
+ Set audit fields on implementation if user ID is available.
1013
+
1014
+ :param ControlImplementation impl: Implementation to update
1015
+ :return: None
1016
+ :rtype: None
1017
+ """
1067
1018
  user_id = self.app.config.get("userId")
1068
1019
  if user_id:
1069
1020
  impl.lastUpdatedById = user_id
1070
1021
  impl.dateLastUpdated = get_current_datetime()
1071
1022
 
1072
- logger.info(f"Marking control {control_id} as {new_status}")
1073
- return impl.dict()
1023
+ def _update_failing_controls_to_in_remediation(self, control_ids: List[str]) -> None:
1024
+ """
1025
+ Update control implementation status to In Remediation for failing controls.
1026
+
1027
+ Uses ControlMatcher for robust control ID matching with leading zero normalization.
1028
+
1029
+ :param List[str] control_ids: List of control IDs that are failing
1030
+ :return: None
1031
+ :rtype: None
1032
+ """
1033
+ if not control_ids:
1034
+ return
1074
1035
 
1075
- def _log_update_summary(self, implementations_to_update: list, controls_not_found: list) -> None:
1076
- """Log summary of control implementation updates."""
1036
+ try:
1037
+ logger.debug(f"Looking for failing control IDs: {control_ids}")
1038
+
1039
+ implementations_to_update = []
1040
+ controls_not_found = []
1041
+
1042
+ for control_id in control_ids:
1043
+ impl_dict = self._prepare_failing_control_update(control_id)
1044
+ if impl_dict:
1045
+ implementations_to_update.append(impl_dict)
1046
+ else:
1047
+ controls_not_found.append(control_id)
1048
+
1049
+ self._log_update_summary(controls_not_found, implementations_to_update)
1050
+ self._batch_update_implementations(implementations_to_update)
1051
+
1052
+ except Exception as e:
1053
+ logger.error(f"Error updating failing control implementation status: {e}")
1054
+
1055
+ def _log_update_summary(self, controls_not_found: List[str], implementations_to_update: List[dict]) -> None:
1056
+ """
1057
+ Log summary of control update operation.
1058
+
1059
+ :param List[str] controls_not_found: List of controls not found
1060
+ :param List[dict] implementations_to_update: List of implementations to update
1061
+ :return: None
1062
+ :rtype: None
1063
+ """
1077
1064
  if controls_not_found:
1078
- skipped_list = ", ".join(controls_not_found[:5])
1079
- more_indicator = "..." if len(controls_not_found) > 5 else ""
1080
- logger.info(
1081
- f"Control implementation status update summary: {len(implementations_to_update)} found, "
1082
- f"{len(controls_not_found)} not in plan (skipped: {skipped_list}{more_indicator})"
1083
- )
1065
+ logger.info(f"Control IDs not found in plan: {', '.join(sorted(controls_not_found))}")
1066
+
1067
+ logger.info(
1068
+ f"Control implementation status update summary: {len(implementations_to_update)} found, "
1069
+ f"{len(controls_not_found)} not in plan"
1070
+ )
1071
+
1072
+ def _batch_update_implementations(self, implementations_to_update: List[dict]) -> None:
1073
+ """
1074
+ Perform batch update of control implementations.
1084
1075
 
1085
- def _batch_update_implementations(self, implementations_to_update: list) -> None:
1086
- """Perform batch update of control implementations."""
1076
+ :param List[dict] implementations_to_update: List of implementations to update
1077
+ :return: None
1078
+ :rtype: None
1079
+ """
1087
1080
  if implementations_to_update:
1088
1081
  ControlImplementation.put_batch_implementation(self.app, implementations_to_update)
1089
1082
  logger.debug(f"Updated {len(implementations_to_update)} Control Implementations, Successfully!")
@@ -1551,6 +1544,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
1551
1544
  rule_id=control_id,
1552
1545
  baseline=representative_item.framework,
1553
1546
  affected_controls=control_id,
1547
+ identification=IssueIdentification.SecurityControlAssessment.value,
1554
1548
  )
1555
1549
 
1556
1550
  def _create_finding_from_compliance_item(self, compliance_item: ComplianceItem) -> Optional[Any]:
@@ -1587,6 +1581,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
1587
1581
  rule_id=compliance_item.control_id,
1588
1582
  baseline=compliance_item.framework,
1589
1583
  affected_controls=compliance_item.affected_controls, # Use our property with all control IDs
1584
+ identification=IssueIdentification.SecurityControlAssessment.value,
1590
1585
  )
1591
1586
 
1592
1587
  return finding
@@ -1171,85 +1171,24 @@ fragment SecuritySubCategoryDetails on SecuritySubCategory {
1171
1171
  }
1172
1172
  """
1173
1173
  DATA_FINDING_QUERY = """
1174
- query DataFindingsGroupedByValueTable($groupBy: DataFindingsGroupedByValueField!, $after: String, $first: Int, $filterBy: DataFindingFilters, $orderBy: DataFindingsGroupedByValueOrder) {
1175
- dataFindingsGroupedByValue(
1176
- groupBy: $groupBy
1174
+ query DataFindingsTable($after: String, $first: Int, $filterBy: DataFindingFiltersV2, $orderBy: DataFindingOrder, $fetchTotalCount: Boolean = true) {
1175
+ dataFindingsV2(
1177
1176
  filterBy: $filterBy
1178
1177
  first: $first
1179
1178
  after: $after
1180
1179
  orderBy: $orderBy
1181
1180
  ) {
1182
1181
  nodes {
1183
- categories
1184
- location {
1185
- countryCode
1186
- state
1187
- }
1188
- regionCount
1189
- graphEntityCount
1190
- graphEntity {
1191
- id
1192
- name
1193
- type
1194
- properties
1195
- projects {
1196
- id
1197
- name
1198
- slug
1199
- isFolder
1200
- }
1201
- issues(filterBy: {status: [OPEN, IN_PROGRESS]}) {
1202
- criticalSeverityCount
1203
- highSeverityCount
1204
- mediumSeverityCount
1205
- lowSeverityCount
1206
- informationalSeverityCount
1207
- }
1208
- }
1209
- cloudAccount {
1210
- id
1211
- name
1212
- externalId
1213
- cloudProvider
1214
- }
1215
- dataClassifiers {
1216
- id
1217
- name
1218
- category
1219
- matcherType
1220
- severity
1221
- }
1222
- securitySubCategories {
1223
- id
1224
- title
1225
- externalId
1226
- description
1227
- category {
1228
- id
1229
- name
1230
- description
1231
- framework {
1232
- id
1233
- name
1234
- description
1235
- enabled
1236
- }
1237
- }
1238
- }
1239
- findingsCount
1240
- dataFindings(first: 5) {
1241
- nodes {
1242
- ...DataFindingDetails
1243
- }
1244
- }
1182
+ ...DataFindingDetails
1245
1183
  }
1246
1184
  pageInfo {
1247
1185
  hasNextPage
1248
1186
  endCursor
1249
1187
  }
1250
- totalCount
1188
+ totalCount @include(if: $fetchTotalCount)
1251
1189
  }
1252
1190
  }
1191
+
1253
1192
  fragment DataFindingDetails on DataFinding {
1254
1193
  id
1255
1194
  name
@@ -1257,10 +1196,10 @@ fragment DataFindingDetails on DataFinding {
1257
1196
  id
1258
1197
  name
1259
1198
  category
1199
+ isTenantSpecific
1260
1200
  securitySubCategories {
1261
1201
  id
1262
1202
  title
1263
- externalId
1264
1203
  description
1265
1204
  category {
1266
1205
  id
@@ -1286,8 +1225,12 @@ fragment DataFindingDetails on DataFinding {
1286
1225
  state
1287
1226
  }
1288
1227
  severity
1228
+ status
1289
1229
  totalMatchCount
1290
1230
  uniqueMatchCount
1231
+ maxUniqueMatchesReached
1232
+ uniqueLocationsCount
1233
+ isEntityPublic
1291
1234
  graphEntity {
1292
1235
  id
1293
1236
  name
@@ -1301,6 +1244,12 @@ fragment DataFindingDetails on DataFinding {
1301
1244
  }
1302
1245
  }
1303
1246
  externalSource
1247
+ details {
1248
+ applicationServices {
1249
+ id
1250
+ displayName
1251
+ }
1252
+ }
1304
1253
  }
1305
1254
  """
1306
1255
 
@@ -1387,14 +1336,14 @@ def get_wiz_vulnerability_queries(project_id: str, filter_by: Optional[dict] = N
1387
1336
  {
1388
1337
  "type": WizVulnerabilityType.DATA_FINDING,
1389
1338
  "query": DATA_FINDING_QUERY,
1390
- "topic_key": "dataFindingsGroupedByValue",
1339
+ "topic_key": "dataFindingsV2",
1391
1340
  "file_path": DATA_FINDINGS_FILE_PATH,
1392
- "asset_lookup": "resource",
1341
+ "asset_lookup": "graphEntity",
1393
1342
  "variables": {
1394
1343
  "first": 200,
1344
+ "fetchTotalCount": True,
1395
1345
  "filterBy": {"projectId": [project_id]},
1396
- "orderBy": {"field": "FINDING_COUNT", "direction": "DESC"},
1397
- "groupBy": "GRAPH_ENTITY",
1346
+ "orderBy": {"field": "TOTAL_MATCHES", "direction": "DESC"},
1398
1347
  },
1399
1348
  },
1400
1349
  {
@@ -1940,10 +1940,9 @@ class WizVulnerabilityIntegration(ScannerIntegration):
1940
1940
  else:
1941
1941
  logger.info("File %s does not exist. Fetching new data...", file_path)
1942
1942
 
1943
- self.authenticate(WizVariables.wizClientId, WizVariables.wizClientSecret)
1944
-
1943
+ # Ensure we have a valid token (should already be set by caller)
1945
1944
  if not self.wiz_token:
1946
- error_and_exit("Wiz token is not set. Please authenticate first.")
1945
+ error_and_exit("Wiz token is not set. Please authenticate before calling fetch_wiz_data_if_needed.")
1947
1946
 
1948
1947
  nodes = fetch_wiz_data(
1949
1948
  query=query,
@@ -1952,6 +1951,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
1952
1951
  token=self.wiz_token,
1953
1952
  topic_key=topic_key,
1954
1953
  )
1954
+
1955
1955
  with open(file_path, "w", encoding="utf-8") as file:
1956
1956
  json.dump(nodes, file)
1957
1957