regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +64 -79
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +161 -165
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +39 -99
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,7 @@ from regscale.integrations.commercial.durosuite.process_devices import scan_duro
|
|
|
27
27
|
from regscale.integrations.commercial.durosuite.variables import DuroSuiteVariables
|
|
28
28
|
from regscale.integrations.commercial.stig_mapper_integration.mapping_engine import StigMappingEngine as STIGMapper
|
|
29
29
|
from regscale.integrations.due_date_handler import DueDateHandler
|
|
30
|
+
from regscale.integrations.milestone_manager import MilestoneManager
|
|
30
31
|
from regscale.integrations.public.cisa import pull_cisa_kev
|
|
31
32
|
from regscale.integrations.variables import ScannerVariables
|
|
32
33
|
from regscale.models import DateTimeEncoder, OpenIssueDict, Property, regscale_models
|
|
@@ -672,6 +673,9 @@ class ScannerIntegration(ABC):
|
|
|
672
673
|
# Initialize due date handler for this integration
|
|
673
674
|
self.due_date_handler = DueDateHandler(self.title, config=self.app.config)
|
|
674
675
|
|
|
676
|
+
# Initialize milestone manager for this integration
|
|
677
|
+
self.milestone_manager = None # Lazy initialization after scan_date is set
|
|
678
|
+
|
|
675
679
|
if self.is_component:
|
|
676
680
|
self.component = regscale_models.Component.get_object(self.plan_id)
|
|
677
681
|
self.parent_module: str = regscale_models.Component.get_module_string()
|
|
@@ -709,6 +713,9 @@ class ScannerIntegration(ABC):
|
|
|
709
713
|
self._no_ccis: bool = False
|
|
710
714
|
self.cci_to_control_map_lock: threading.Lock = threading.Lock()
|
|
711
715
|
|
|
716
|
+
# Lock for thread-safe scan history count updates
|
|
717
|
+
self.scan_history_lock: threading.RLock = threading.RLock()
|
|
718
|
+
|
|
712
719
|
self.assessment_map: ThreadSafeDict[int, regscale_models.Assessment] = ThreadSafeDict()
|
|
713
720
|
self.assessor_id: str = self.get_assessor_id()
|
|
714
721
|
self.asset_progress: Progress = create_progress_object()
|
|
@@ -737,6 +744,21 @@ class ScannerIntegration(ABC):
|
|
|
737
744
|
cls._lock_registry[key] = lock
|
|
738
745
|
return lock
|
|
739
746
|
|
|
747
|
+
def get_milestone_manager(self) -> MilestoneManager:
|
|
748
|
+
"""
|
|
749
|
+
Get or initialize the milestone manager.
|
|
750
|
+
|
|
751
|
+
:return: MilestoneManager instance
|
|
752
|
+
:rtype: MilestoneManager
|
|
753
|
+
"""
|
|
754
|
+
if self.milestone_manager is None:
|
|
755
|
+
self.milestone_manager = MilestoneManager(
|
|
756
|
+
integration_title=self.title,
|
|
757
|
+
assessor_id=self.assessor_id,
|
|
758
|
+
scan_date=self.scan_date or get_current_datetime(),
|
|
759
|
+
)
|
|
760
|
+
return self.milestone_manager
|
|
761
|
+
|
|
740
762
|
@staticmethod
|
|
741
763
|
def load_stig_mapper() -> Optional[STIGMapper]:
|
|
742
764
|
"""
|
|
@@ -997,15 +1019,18 @@ class ScannerIntegration(ABC):
|
|
|
997
1019
|
return res[:450]
|
|
998
1020
|
return prefix[:450]
|
|
999
1021
|
|
|
1000
|
-
def get_or_create_assessment(
|
|
1022
|
+
def get_or_create_assessment(
|
|
1023
|
+
self, control_implementation_id: int, status: Optional[regscale_models.AssessmentResultsStatus] = None
|
|
1024
|
+
) -> regscale_models.Assessment:
|
|
1001
1025
|
"""
|
|
1002
|
-
Gets or creates a RegScale assessment
|
|
1026
|
+
Gets or creates a RegScale assessment.
|
|
1003
1027
|
|
|
1004
1028
|
:param int control_implementation_id: The ID of the control implementation
|
|
1029
|
+
:param Optional[regscale_models.AssessmentResultsStatus] status: Optional status override (used by cci_assessment)
|
|
1005
1030
|
:return: The assessment
|
|
1006
1031
|
:rtype: regscale_models.Assessment
|
|
1007
1032
|
"""
|
|
1008
|
-
logger.
|
|
1033
|
+
logger.debug("Getting or create assessment for control implementation %d", control_implementation_id)
|
|
1009
1034
|
assessment: Optional[regscale_models.Assessment] = self.assessment_map.get(control_implementation_id)
|
|
1010
1035
|
if assessment:
|
|
1011
1036
|
logger.debug(
|
|
@@ -1017,7 +1042,7 @@ class ScannerIntegration(ABC):
|
|
|
1017
1042
|
plannedStart=get_current_datetime(),
|
|
1018
1043
|
plannedFinish=get_current_datetime(),
|
|
1019
1044
|
status=regscale_models.AssessmentStatus.COMPLETE.value,
|
|
1020
|
-
assessmentResult=regscale_models.AssessmentResultsStatus.FAIL.value,
|
|
1045
|
+
assessmentResult=status.value if status else regscale_models.AssessmentResultsStatus.FAIL.value,
|
|
1021
1046
|
actualFinish=get_current_datetime(),
|
|
1022
1047
|
leadAssessorId=self.assessor_id,
|
|
1023
1048
|
parentId=control_implementation_id,
|
|
@@ -2284,87 +2309,26 @@ class ScannerIntegration(ABC):
|
|
|
2284
2309
|
"""
|
|
2285
2310
|
Create milestones for an issue based on status transitions.
|
|
2286
2311
|
|
|
2312
|
+
Delegates to MilestoneManager for cleaner separation of concerns.
|
|
2313
|
+
Also ensures existing issues have creation milestones (backfills if missing).
|
|
2314
|
+
|
|
2287
2315
|
:param regscale_models.Issue issue: The issue to create milestones for
|
|
2288
2316
|
:param IntegrationFinding finding: The finding data
|
|
2289
2317
|
:param Optional[regscale_models.Issue] existing_issue: Existing issue for comparison
|
|
2290
2318
|
"""
|
|
2291
|
-
|
|
2292
|
-
return
|
|
2293
|
-
|
|
2294
|
-
if self._should_create_reopened_milestone(existing_issue, issue):
|
|
2295
|
-
self._create_milestone_safe(
|
|
2296
|
-
issue, finding, "Issue reopened from", get_current_datetime(), "reopened milestone"
|
|
2297
|
-
)
|
|
2298
|
-
elif self._should_create_closed_milestone(existing_issue, issue):
|
|
2299
|
-
self._create_milestone_safe(issue, finding, "Issue closed from", issue.dateCompleted, "closed milestone")
|
|
2300
|
-
elif not existing_issue:
|
|
2301
|
-
self._create_milestone_safe(issue, finding, "Issue created from", self.scan_date, "new issue milestone")
|
|
2302
|
-
else:
|
|
2303
|
-
logger.debug("No milestone created for issue %s from finding %s", issue.id, finding.external_id)
|
|
2304
|
-
|
|
2305
|
-
def _should_create_reopened_milestone(
|
|
2306
|
-
self, existing_issue: Optional[regscale_models.Issue], issue: regscale_models.Issue
|
|
2307
|
-
) -> bool:
|
|
2308
|
-
"""
|
|
2309
|
-
Check if a reopened milestone should be created.
|
|
2319
|
+
milestone_manager = self.get_milestone_manager()
|
|
2310
2320
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
:rtype: bool
|
|
2315
|
-
"""
|
|
2316
|
-
return (
|
|
2317
|
-
existing_issue
|
|
2318
|
-
and existing_issue.status == regscale_models.IssueStatus.Closed
|
|
2319
|
-
and issue.status == regscale_models.IssueStatus.Open
|
|
2320
|
-
)
|
|
2321
|
-
|
|
2322
|
-
def _should_create_closed_milestone(
|
|
2323
|
-
self, existing_issue: Optional[regscale_models.Issue], issue: regscale_models.Issue
|
|
2324
|
-
) -> bool:
|
|
2325
|
-
"""
|
|
2326
|
-
Check if a closed milestone should be created.
|
|
2321
|
+
# For existing issues, ensure they have a creation milestone (backfill if missing)
|
|
2322
|
+
if existing_issue:
|
|
2323
|
+
milestone_manager.ensure_creation_milestone_exists(issue=issue, finding=finding)
|
|
2327
2324
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
return (
|
|
2334
|
-
existing_issue
|
|
2335
|
-
and existing_issue.status == regscale_models.IssueStatus.Open
|
|
2336
|
-
and issue.status == regscale_models.IssueStatus.Closed
|
|
2325
|
+
# Handle status transition milestones
|
|
2326
|
+
milestone_manager.create_milestones_for_issue(
|
|
2327
|
+
issue=issue,
|
|
2328
|
+
finding=finding,
|
|
2329
|
+
existing_issue=existing_issue,
|
|
2337
2330
|
)
|
|
2338
2331
|
|
|
2339
|
-
def _create_milestone_safe(
|
|
2340
|
-
self,
|
|
2341
|
-
issue: regscale_models.Issue,
|
|
2342
|
-
finding: IntegrationFinding,
|
|
2343
|
-
title_prefix: str,
|
|
2344
|
-
milestone_date: str,
|
|
2345
|
-
milestone_type: str,
|
|
2346
|
-
) -> None:
|
|
2347
|
-
"""
|
|
2348
|
-
Safely create a milestone with error handling.
|
|
2349
|
-
|
|
2350
|
-
:param regscale_models.Issue issue: The issue to create milestone for
|
|
2351
|
-
:param IntegrationFinding finding: The finding data
|
|
2352
|
-
:param str title_prefix: Prefix for milestone title
|
|
2353
|
-
:param str milestone_date: Date for the milestone
|
|
2354
|
-
:param str milestone_type: Description for logging purposes
|
|
2355
|
-
"""
|
|
2356
|
-
try:
|
|
2357
|
-
regscale_models.Milestone(
|
|
2358
|
-
title=f"{title_prefix} {self.title} scan",
|
|
2359
|
-
milestoneDate=milestone_date,
|
|
2360
|
-
responsiblePersonId=self.assessor_id,
|
|
2361
|
-
parentID=issue.id,
|
|
2362
|
-
parentModule="issues",
|
|
2363
|
-
).create_or_update()
|
|
2364
|
-
logger.debug("Added milestone for issue %s from finding %s", issue.id, finding.external_id)
|
|
2365
|
-
except Exception as e:
|
|
2366
|
-
logger.warning("Failed to create %s: %s", milestone_type, str(e))
|
|
2367
|
-
|
|
2368
2332
|
@staticmethod
|
|
2369
2333
|
def extra_data_to_properties(finding: IntegrationFinding, issue_id: int) -> None:
|
|
2370
2334
|
"""
|
|
@@ -2530,6 +2494,8 @@ class ScannerIntegration(ABC):
|
|
|
2530
2494
|
if found_issue.controlImplementationIds:
|
|
2531
2495
|
for control_id in found_issue.controlImplementationIds:
|
|
2532
2496
|
self.update_control_implementation_status_after_close(control_id)
|
|
2497
|
+
# Update assessment status to reflect the control implementation status
|
|
2498
|
+
self.update_assessment_status_from_control_implementation(control_id)
|
|
2533
2499
|
|
|
2534
2500
|
def handle_failing_checklist(
|
|
2535
2501
|
self,
|
|
@@ -2554,11 +2520,13 @@ class ScannerIntegration(ABC):
|
|
|
2554
2520
|
if failing_objective.name.lower().startswith("cci-"):
|
|
2555
2521
|
implementation_id = self.get_control_implementation_id_for_cci(failing_objective.name)
|
|
2556
2522
|
else:
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2523
|
+
implementation_id = self._fallback_implementation_id(failing_objective)
|
|
2524
|
+
|
|
2525
|
+
if not implementation_id or implementation_id is None:
|
|
2526
|
+
logger.warning(
|
|
2527
|
+
"Could not map objective to a Control Implementation for objective #%i.", failing_objective.id
|
|
2528
|
+
)
|
|
2529
|
+
continue
|
|
2562
2530
|
|
|
2563
2531
|
failing_option = regscale_models.ImplementationOption(
|
|
2564
2532
|
name="Failed STIG",
|
|
@@ -2580,13 +2548,36 @@ class ScannerIntegration(ABC):
|
|
|
2580
2548
|
).create_or_update()
|
|
2581
2549
|
|
|
2582
2550
|
# Create assessment and control test result
|
|
2583
|
-
assessment = self.get_or_create_assessment(
|
|
2551
|
+
assessment = self.get_or_create_assessment(
|
|
2552
|
+
implementation_id, status=regscale_models.AssessmentResultsStatus.FAIL
|
|
2553
|
+
)
|
|
2584
2554
|
if implementation_id:
|
|
2585
2555
|
control_test = self.create_or_get_control_test(finding, implementation_id)
|
|
2586
2556
|
self.create_control_test_result(
|
|
2587
2557
|
finding, control_test, assessment, regscale_models.ControlTestResultStatus.FAIL
|
|
2588
2558
|
)
|
|
2589
2559
|
|
|
2560
|
+
def _fallback_implementation_id(self, objective: regscale_models.ControlObjective) -> Optional[int]:
|
|
2561
|
+
"""
|
|
2562
|
+
Fallback method to get control implementation ID from objective name if CCI mapping fails.
|
|
2563
|
+
|
|
2564
|
+
:param regscale_models.ControlObjective objective: The control objective
|
|
2565
|
+
:return: The control implementation ID if found, None otherwise
|
|
2566
|
+
:rtype: Optional[int]
|
|
2567
|
+
"""
|
|
2568
|
+
control_label = objective_to_control_dot(objective.name)
|
|
2569
|
+
if implementation_id := self.control_implementation_id_map.get(control_label):
|
|
2570
|
+
return implementation_id
|
|
2571
|
+
|
|
2572
|
+
if control_id := self.control_id_to_implementation_map.get(objective.securityControlId):
|
|
2573
|
+
if control_label := self.control_map.get(control_id):
|
|
2574
|
+
implementation_id = self.control_implementation_id_map.get(control_label)
|
|
2575
|
+
if not implementation_id:
|
|
2576
|
+
print("No dice.")
|
|
2577
|
+
return implementation_id
|
|
2578
|
+
logger.debug("Could not find fallback implementation ID for objective #%i", objective.id)
|
|
2579
|
+
return None
|
|
2580
|
+
|
|
2590
2581
|
def handle_passing_checklist(
|
|
2591
2582
|
self,
|
|
2592
2583
|
finding: IntegrationFinding,
|
|
@@ -2610,15 +2601,12 @@ class ScannerIntegration(ABC):
|
|
|
2610
2601
|
if passing_objective.name.lower().startswith("cci-"):
|
|
2611
2602
|
implementation_id = self.get_control_implementation_id_for_cci(passing_objective.name)
|
|
2612
2603
|
else:
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
# Skip if we couldn't determine the implementation ID
|
|
2620
|
-
if implementation_id is None:
|
|
2621
|
-
logger.warning("Could not determine implementation ID for objective %s", passing_objective.name)
|
|
2604
|
+
implementation_id = self._fallback_implementation_id(passing_objective)
|
|
2605
|
+
|
|
2606
|
+
if not implementation_id or implementation_id is None:
|
|
2607
|
+
logger.warning(
|
|
2608
|
+
"Could not map objective to a Control Implementation for objective #%i.", passing_objective.id
|
|
2609
|
+
)
|
|
2622
2610
|
continue
|
|
2623
2611
|
|
|
2624
2612
|
passing_option = regscale_models.ImplementationOption(
|
|
@@ -2641,7 +2629,9 @@ class ScannerIntegration(ABC):
|
|
|
2641
2629
|
).create_or_update()
|
|
2642
2630
|
|
|
2643
2631
|
# Create assessment and control test result
|
|
2644
|
-
assessment = self.get_or_create_assessment(
|
|
2632
|
+
assessment = self.get_or_create_assessment(
|
|
2633
|
+
implementation_id, status=regscale_models.AssessmentResultsStatus.PASS
|
|
2634
|
+
)
|
|
2645
2635
|
control_test = self.create_or_get_control_test(finding, implementation_id)
|
|
2646
2636
|
self.create_control_test_result(
|
|
2647
2637
|
finding, control_test, assessment, regscale_models.ControlTestResultStatus.PASS
|
|
@@ -2667,17 +2657,45 @@ class ScannerIntegration(ABC):
|
|
|
2667
2657
|
|
|
2668
2658
|
def get_asset_by_identifier(self, identifier: str) -> Optional[regscale_models.Asset]:
|
|
2669
2659
|
"""
|
|
2670
|
-
Gets an asset by its identifier
|
|
2660
|
+
Gets an asset by its identifier with fallback lookups.
|
|
2661
|
+
|
|
2662
|
+
REG-17044: Enhanced to support multiple identifier fields (qualysId, IP, FQDN)
|
|
2663
|
+
to improve asset matching and reduce "asset not found" errors.
|
|
2671
2664
|
|
|
2672
2665
|
:param str identifier: The identifier of the asset
|
|
2673
2666
|
:return: The asset
|
|
2674
2667
|
:rtype: Optional[regscale_models.Asset]
|
|
2675
2668
|
"""
|
|
2676
|
-
|
|
2669
|
+
# Try primary identifier field first
|
|
2670
|
+
if asset := self.asset_map_by_identifier.get(identifier):
|
|
2671
|
+
return asset
|
|
2672
|
+
|
|
2673
|
+
# Fallback: Try common identifier fields
|
|
2674
|
+
# This helps when asset_identifier_field doesn't match or assets use different identifiers
|
|
2675
|
+
if not asset and identifier:
|
|
2676
|
+
for cached_asset in self.asset_map_by_identifier.values():
|
|
2677
|
+
# Try IP address lookup
|
|
2678
|
+
if getattr(cached_asset, "ipAddress", None) == identifier:
|
|
2679
|
+
logger.debug(f"Found asset {cached_asset.id} by IP address fallback: {identifier}")
|
|
2680
|
+
return cached_asset
|
|
2681
|
+
# Try FQDN lookup
|
|
2682
|
+
if getattr(cached_asset, "fqdn", None) == identifier:
|
|
2683
|
+
logger.debug(f"Found asset {cached_asset.id} by FQDN fallback: {identifier}")
|
|
2684
|
+
return cached_asset
|
|
2685
|
+
# Try DNS lookup
|
|
2686
|
+
if getattr(cached_asset, "dns", None) == identifier:
|
|
2687
|
+
logger.debug(f"Found asset {cached_asset.id} by DNS fallback: {identifier}")
|
|
2688
|
+
return cached_asset
|
|
2689
|
+
|
|
2690
|
+
# Log error if still not found
|
|
2677
2691
|
if not asset and identifier not in self.alerted_assets:
|
|
2678
2692
|
self.alerted_assets.add(identifier)
|
|
2679
2693
|
if not getattr(self, "suppress_asset_not_found_errors", False):
|
|
2680
|
-
self.log_error(
|
|
2694
|
+
self.log_error(
|
|
2695
|
+
"Asset not found for identifier '%s' (tried %s, ipAddress, fqdn, dns)",
|
|
2696
|
+
identifier,
|
|
2697
|
+
self.asset_identifier_field,
|
|
2698
|
+
)
|
|
2681
2699
|
return asset
|
|
2682
2700
|
|
|
2683
2701
|
def get_issue_by_integration_finding_id(self, integration_finding_id: str) -> Optional[regscale_models.Issue]:
|
|
@@ -2706,7 +2724,11 @@ class ScannerIntegration(ABC):
|
|
|
2706
2724
|
logger.error("2. Asset not found for identifier %s", finding.asset_identifier)
|
|
2707
2725
|
return 0
|
|
2708
2726
|
|
|
2709
|
-
tool =
|
|
2727
|
+
tool = (
|
|
2728
|
+
regscale_models.ChecklistTool.CISBenchmarks
|
|
2729
|
+
if "simp.cis" in str(finding.vulnerability_number).lower()
|
|
2730
|
+
else regscale_models.ChecklistTool.STIGs
|
|
2731
|
+
)
|
|
2710
2732
|
if finding.vulnerability_type == "Vulnerability Scan":
|
|
2711
2733
|
tool = regscale_models.ChecklistTool.VulnerabilityScanner
|
|
2712
2734
|
|
|
@@ -2871,7 +2893,7 @@ class ScannerIntegration(ABC):
|
|
|
2871
2893
|
def _finalize_finding_processing(
|
|
2872
2894
|
self, scan_history: regscale_models.ScanHistory, current_vulnerabilities: Dict[int, Set[int]]
|
|
2873
2895
|
) -> None:
|
|
2874
|
-
"""Finalize the finding processing by saving scan history and closing outdated issues."""
|
|
2896
|
+
"""Finalize the finding processing by saving scan history and closing outdated vulnerabilities and issues."""
|
|
2875
2897
|
logger.info(
|
|
2876
2898
|
f"Saving scan history with final counts - Low: {scan_history.vLow}, Medium: {scan_history.vMedium}, High: {scan_history.vHigh}, Critical: {scan_history.vCritical}, Info: {scan_history.vInfo}"
|
|
2877
2899
|
)
|
|
@@ -2890,6 +2912,7 @@ class ScannerIntegration(ABC):
|
|
|
2890
2912
|
|
|
2891
2913
|
self._results["scan_history"] = scan_history
|
|
2892
2914
|
self.update_result_counts("issues", regscale_models.Issue.bulk_save(progress_context=self.finding_progress))
|
|
2915
|
+
self.close_outdated_vulnerabilities(current_vulnerabilities)
|
|
2893
2916
|
self.close_outdated_issues(current_vulnerabilities)
|
|
2894
2917
|
self._perform_batch_operations(self.finding_progress)
|
|
2895
2918
|
|
|
@@ -2995,24 +3018,25 @@ class ScannerIntegration(ABC):
|
|
|
2995
3018
|
self._process_checklist_finding(finding)
|
|
2996
3019
|
|
|
2997
3020
|
# Process vulnerability if applicable
|
|
2998
|
-
|
|
2999
|
-
|
|
3021
|
+
# IMPORTANT: Always track vulnerabilities regardless of status to enable proper issue closure logic
|
|
3022
|
+
# This ensures that current_vulnerabilities dict accurately reflects the scan state
|
|
3023
|
+
vulnerability_created = self._process_vulnerability_finding(finding, scan_history, current_vulnerabilities)
|
|
3000
3024
|
|
|
3025
|
+
# Only create/update issues for non-closed findings (unless ingestClosedIssues is enabled)
|
|
3026
|
+
if finding.status != regscale_models.IssueStatus.Closed or ScannerVariables.ingestClosedIssues:
|
|
3001
3027
|
self.handle_failing_finding(
|
|
3002
3028
|
issue_title=finding.issue_title or finding.title,
|
|
3003
3029
|
finding=finding,
|
|
3004
3030
|
)
|
|
3005
3031
|
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
f"Skipping severity count update for finding {finding.external_id} - no vulnerability created"
|
|
3015
|
-
)
|
|
3032
|
+
# Update scan history severity counts only if vulnerability was successfully created
|
|
3033
|
+
if vulnerability_created:
|
|
3034
|
+
logger.debug(
|
|
3035
|
+
f"Updating severity count for successfully created vulnerability with severity: {finding.severity}"
|
|
3036
|
+
)
|
|
3037
|
+
self.set_severity_count_for_scan(finding.severity, scan_history, self.scan_history_lock)
|
|
3038
|
+
else:
|
|
3039
|
+
logger.debug(f"Skipping severity count update for finding {finding.external_id} - no vulnerability created")
|
|
3016
3040
|
|
|
3017
3041
|
def _process_checklist_finding(self, finding: IntegrationFinding) -> None:
|
|
3018
3042
|
"""Process a checklist finding."""
|
|
@@ -3269,13 +3293,34 @@ class ScannerIntegration(ABC):
|
|
|
3269
3293
|
vuln_list.append(vuln)
|
|
3270
3294
|
return vuln_list
|
|
3271
3295
|
|
|
3272
|
-
def close_outdated_vulnerabilities(self, current_vulnerabilities: Dict[int, Set[int]]) ->
|
|
3296
|
+
def close_outdated_vulnerabilities(self, current_vulnerabilities: Dict[int, Set[int]]) -> int:
|
|
3273
3297
|
"""
|
|
3274
3298
|
Closes vulnerabilities that are not in the current set of vulnerability IDs for each asset.
|
|
3275
3299
|
|
|
3276
3300
|
:param Dict[int, Set[int]] current_vulnerabilities: Dictionary of asset IDs to lists of current vulnerability IDs
|
|
3277
|
-
:
|
|
3301
|
+
:return: Number of vulnerabilities closed
|
|
3302
|
+
:rtype: int
|
|
3278
3303
|
"""
|
|
3304
|
+
if not self.close_outdated_findings:
|
|
3305
|
+
logger.info("Skipping closing outdated vulnerabilities.")
|
|
3306
|
+
return 0
|
|
3307
|
+
|
|
3308
|
+
# Check global preventAutoClose setting
|
|
3309
|
+
from regscale.core.app.application import Application
|
|
3310
|
+
|
|
3311
|
+
app = Application()
|
|
3312
|
+
if app.config.get("preventAutoClose", False):
|
|
3313
|
+
logger.info("Skipping closing outdated vulnerabilities due to global preventAutoClose setting.")
|
|
3314
|
+
return 0
|
|
3315
|
+
|
|
3316
|
+
# REG-17044: Add defensive logging to track vulnerability closure state
|
|
3317
|
+
logger.debug(f"Vulnerability Closure Analysis for {self.title}:")
|
|
3318
|
+
logger.debug(f" - Assets with current vulnerabilities: {len(current_vulnerabilities)}")
|
|
3319
|
+
total_current_vulns = sum(len(vuln_set) for vuln_set in current_vulnerabilities.values())
|
|
3320
|
+
logger.debug(f" - Total current vulnerabilities tracked: {total_current_vulns}")
|
|
3321
|
+
if total_current_vulns == 0:
|
|
3322
|
+
logger.warning("No current vulnerabilities tracked - this may close all vulnerabilities!")
|
|
3323
|
+
|
|
3279
3324
|
# Get all current vulnerability IDs
|
|
3280
3325
|
current_vuln_ids = {vuln_id for vuln_ids in current_vulnerabilities.values() for vuln_id in vuln_ids}
|
|
3281
3326
|
|
|
@@ -3298,9 +3343,14 @@ class ScannerIntegration(ABC):
|
|
|
3298
3343
|
vuln.dateClosed = get_current_datetime()
|
|
3299
3344
|
vuln.save()
|
|
3300
3345
|
closed_count += 1
|
|
3301
|
-
logger.
|
|
3346
|
+
logger.debug("Closed vulnerability %d", vuln.id)
|
|
3302
3347
|
|
|
3303
|
-
|
|
3348
|
+
(
|
|
3349
|
+
logger.info("Closed %d outdated vulnerabilities.", closed_count)
|
|
3350
|
+
if closed_count > 0
|
|
3351
|
+
else logger.info("No outdated vulnerabilities to close.")
|
|
3352
|
+
)
|
|
3353
|
+
return closed_count
|
|
3304
3354
|
|
|
3305
3355
|
@classmethod
|
|
3306
3356
|
def close_mappings_list(cls, vuln: regscale_models.Vulnerability) -> None:
|
|
@@ -3350,6 +3400,13 @@ class ScannerIntegration(ABC):
|
|
|
3350
3400
|
logger.info("Skipping closing outdated issues due to global preventAutoClose setting.")
|
|
3351
3401
|
return 0
|
|
3352
3402
|
|
|
3403
|
+
# REG-17044: Add defensive logging to track issue closure state
|
|
3404
|
+
logger.debug(f"Issue Closure Analysis for {self.title}:")
|
|
3405
|
+
total_current_vulns = sum(len(vuln_set) for vuln_set in current_vulnerabilities.values())
|
|
3406
|
+
logger.debug(f" - Total current vulnerabilities to check against: {total_current_vulns}")
|
|
3407
|
+
if total_current_vulns == 0:
|
|
3408
|
+
logger.warning("No current vulnerabilities tracked - this may close all issues!")
|
|
3409
|
+
|
|
3353
3410
|
closed_count = 0
|
|
3354
3411
|
affected_control_ids = set()
|
|
3355
3412
|
count_lock = threading.Lock()
|
|
@@ -3381,6 +3438,8 @@ class ScannerIntegration(ABC):
|
|
|
3381
3438
|
|
|
3382
3439
|
for control_id in affected_control_ids:
|
|
3383
3440
|
self.update_control_implementation_status_after_close(control_id)
|
|
3441
|
+
# Update assessment status to reflect the control implementation status
|
|
3442
|
+
self.update_assessment_status_from_control_implementation(control_id)
|
|
3384
3443
|
|
|
3385
3444
|
(
|
|
3386
3445
|
logger.info("Closed %d outdated issues.", closed_count)
|
|
@@ -3483,9 +3542,70 @@ class ScannerIntegration(ABC):
|
|
|
3483
3542
|
if control_implementation.status != new_status:
|
|
3484
3543
|
control_implementation.status = new_status
|
|
3485
3544
|
self.control_implementation_map[control_id] = control_implementation.save()
|
|
3486
|
-
logger.
|
|
3545
|
+
logger.debug("Updated control implementation %d status to %s", control_id, new_status)
|
|
3487
3546
|
|
|
3488
|
-
def
|
|
3547
|
+
def update_assessment_status_from_control_implementation(self, control_implementation_id: int) -> None:
|
|
3548
|
+
"""
|
|
3549
|
+
Updates the assessment status based on the control implementation status.
|
|
3550
|
+
Treats the ControlImplementation status as the source of truth.
|
|
3551
|
+
|
|
3552
|
+
Sets assessment to PASS if ControlImplementation status is FULLY_IMPLEMENTED,
|
|
3553
|
+
otherwise sets it to FAIL.
|
|
3554
|
+
|
|
3555
|
+
This method should be called after update_control_implementation_status_after_close
|
|
3556
|
+
to ensure assessments reflect the final control implementation state.
|
|
3557
|
+
|
|
3558
|
+
:param int control_implementation_id: The ID of the control implementation
|
|
3559
|
+
:rtype: None
|
|
3560
|
+
"""
|
|
3561
|
+
# Get the cached assessment for this control implementation
|
|
3562
|
+
assessment = self.assessment_map.get(control_implementation_id)
|
|
3563
|
+
|
|
3564
|
+
if not assessment:
|
|
3565
|
+
logger.debug(
|
|
3566
|
+
"No assessment found in cache for control implementation %d, skipping assessment update",
|
|
3567
|
+
control_implementation_id,
|
|
3568
|
+
)
|
|
3569
|
+
return
|
|
3570
|
+
|
|
3571
|
+
# Get the control implementation to check its status
|
|
3572
|
+
control_implementation = self.control_implementation_map.get(
|
|
3573
|
+
control_implementation_id
|
|
3574
|
+
) or regscale_models.ControlImplementation.get_object(object_id=control_implementation_id)
|
|
3575
|
+
|
|
3576
|
+
if not control_implementation:
|
|
3577
|
+
logger.warning("Control implementation %d not found, cannot update assessment", control_implementation_id)
|
|
3578
|
+
return
|
|
3579
|
+
|
|
3580
|
+
# Determine assessment result based on control implementation status
|
|
3581
|
+
# Treat ControlImplementation status as the source of truth
|
|
3582
|
+
new_assessment_result = (
|
|
3583
|
+
regscale_models.AssessmentResultsStatus.PASS
|
|
3584
|
+
if control_implementation.status == regscale_models.ImplementationStatus.FULLY_IMPLEMENTED.value
|
|
3585
|
+
else regscale_models.AssessmentResultsStatus.FAIL
|
|
3586
|
+
)
|
|
3587
|
+
|
|
3588
|
+
# Only update if the status has changed
|
|
3589
|
+
if assessment.assessmentResult != new_assessment_result.value:
|
|
3590
|
+
assessment.assessmentResult = new_assessment_result.value
|
|
3591
|
+
assessment.save()
|
|
3592
|
+
logger.debug(
|
|
3593
|
+
"Updated assessment %d for control implementation %d: assessmentResult=%s (based on control status: %s)",
|
|
3594
|
+
assessment.id,
|
|
3595
|
+
control_implementation_id,
|
|
3596
|
+
new_assessment_result.value,
|
|
3597
|
+
control_implementation.status,
|
|
3598
|
+
)
|
|
3599
|
+
else:
|
|
3600
|
+
logger.debug(
|
|
3601
|
+
"Assessment %d already has correct status %s for control implementation %d",
|
|
3602
|
+
assessment.id,
|
|
3603
|
+
assessment.assessmentResult,
|
|
3604
|
+
control_implementation_id,
|
|
3605
|
+
)
|
|
3606
|
+
|
|
3607
|
+
@staticmethod
|
|
3608
|
+
def is_issue_protected_from_auto_close(issue: regscale_models.Issue) -> bool:
|
|
3489
3609
|
"""
|
|
3490
3610
|
Check if an issue is protected from automatic closure.
|
|
3491
3611
|
|
|
@@ -3567,51 +3687,55 @@ class ScannerIntegration(ABC):
|
|
|
3567
3687
|
return True
|
|
3568
3688
|
|
|
3569
3689
|
@staticmethod
|
|
3570
|
-
def set_severity_count_for_scan(
|
|
3690
|
+
def set_severity_count_for_scan(
|
|
3691
|
+
severity: str, scan_history: regscale_models.ScanHistory, lock: Optional[threading.RLock] = None
|
|
3692
|
+
) -> None:
|
|
3571
3693
|
"""
|
|
3572
|
-
Increments the count of the severity
|
|
3694
|
+
Increments the count of the severity in a thread-safe manner.
|
|
3695
|
+
|
|
3696
|
+
NOTE: This method does NOT save the scan_history object. The caller is responsible
|
|
3697
|
+
for saving the scan_history after all increments are complete to avoid race conditions
|
|
3698
|
+
and excessive database writes in multi-threaded environments.
|
|
3699
|
+
|
|
3573
3700
|
:param str severity: Severity of the vulnerability
|
|
3574
3701
|
:param regscale_models.ScanHistory scan_history: Scan history object
|
|
3702
|
+
:param Optional[threading.RLock] lock: Thread lock for synchronization (recommended in multi-threaded context)
|
|
3575
3703
|
:rtype: None
|
|
3576
3704
|
"""
|
|
3577
|
-
logger.debug(f"Setting severity count for scan {scan_history.id}: severity='{severity}'")
|
|
3578
|
-
logger.debug(
|
|
3579
|
-
f"Current counts - Low: {scan_history.vLow}, Medium: {scan_history.vMedium}, High: {scan_history.vHigh}, Critical: {scan_history.vCritical}, Info: {scan_history.vInfo}"
|
|
3580
|
-
)
|
|
3581
3705
|
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
logger.debug(f"
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
elif severity == regscale_models.IssueSeverity.High.value:
|
|
3589
|
-
scan_history.vHigh += 1
|
|
3590
|
-
logger.debug(f"Incremented vHigh count to {scan_history.vHigh}")
|
|
3591
|
-
elif severity == regscale_models.IssueSeverity.Critical.value:
|
|
3592
|
-
scan_history.vCritical += 1
|
|
3593
|
-
logger.debug(f"Incremented vCritical count to {scan_history.vCritical}")
|
|
3594
|
-
else:
|
|
3595
|
-
scan_history.vInfo += 1
|
|
3596
|
-
logger.debug(f"Incremented vInfo count to {scan_history.vInfo}")
|
|
3706
|
+
def _increment_severity():
|
|
3707
|
+
"""Internal method to perform the actual increment."""
|
|
3708
|
+
logger.debug(f"Setting severity count for scan {scan_history.id}: severity='{severity}'")
|
|
3709
|
+
logger.debug(
|
|
3710
|
+
f"Current counts - Low: {scan_history.vLow}, Medium: {scan_history.vMedium}, High: {scan_history.vHigh}, Critical: {scan_history.vCritical}, Info: {scan_history.vInfo}"
|
|
3711
|
+
)
|
|
3597
3712
|
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3713
|
+
if severity.lower() == regscale_models.IssueSeverity.Low.value.lower():
|
|
3714
|
+
scan_history.vLow += 1
|
|
3715
|
+
logger.debug(f"Incremented vLow count to {scan_history.vLow}")
|
|
3716
|
+
elif severity.lower() == regscale_models.IssueSeverity.Moderate.value.lower():
|
|
3717
|
+
scan_history.vMedium += 1
|
|
3718
|
+
logger.debug(f"Incremented vMedium count to {scan_history.vMedium}")
|
|
3719
|
+
elif severity.lower() == regscale_models.IssueSeverity.High.value.lower():
|
|
3720
|
+
scan_history.vHigh += 1
|
|
3721
|
+
logger.debug(f"Incremented vHigh count to {scan_history.vHigh}")
|
|
3722
|
+
elif severity.lower() == regscale_models.IssueSeverity.Critical.value.lower():
|
|
3723
|
+
scan_history.vCritical += 1
|
|
3724
|
+
logger.debug(f"Incremented vCritical count to {scan_history.vCritical}")
|
|
3725
|
+
else:
|
|
3726
|
+
scan_history.vInfo += 1
|
|
3727
|
+
logger.debug(f"Incremented vInfo count to {scan_history.vInfo}")
|
|
3601
3728
|
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
logger.debug(f"Successfully saved scan history {scan_history.id} after retry")
|
|
3613
|
-
except Exception as e2:
|
|
3614
|
-
logger.error(f"Failed to save scan history {scan_history.id} after retry: {e2}")
|
|
3729
|
+
logger.debug(
|
|
3730
|
+
f"Updated counts - Low: {scan_history.vLow}, Medium: {scan_history.vMedium}, High: {scan_history.vHigh}, Critical: {scan_history.vCritical}, Info: {scan_history.vInfo}"
|
|
3731
|
+
)
|
|
3732
|
+
|
|
3733
|
+
# Use lock if provided for thread-safe increments
|
|
3734
|
+
if lock:
|
|
3735
|
+
with lock:
|
|
3736
|
+
_increment_severity()
|
|
3737
|
+
else:
|
|
3738
|
+
_increment_severity()
|
|
3615
3739
|
|
|
3616
3740
|
@classmethod
|
|
3617
3741
|
def cci_assessment(cls, plan_id: int) -> None:
|