regscale-cli 6.21.1.0__py3-none-any.whl → 6.21.2.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.
Files changed (34) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +7 -0
  3. regscale/integrations/commercial/__init__.py +8 -8
  4. regscale/integrations/commercial/import_all/import_all_cmd.py +2 -2
  5. regscale/integrations/commercial/microsoft_defender/__init__.py +0 -0
  6. regscale/integrations/commercial/{defender.py → microsoft_defender/defender.py} +38 -612
  7. regscale/integrations/commercial/microsoft_defender/defender_api.py +286 -0
  8. regscale/integrations/commercial/microsoft_defender/defender_constants.py +80 -0
  9. regscale/integrations/commercial/microsoft_defender/defender_scanner.py +168 -0
  10. regscale/integrations/commercial/qualys/__init__.py +24 -86
  11. regscale/integrations/commercial/qualys/containers.py +2 -0
  12. regscale/integrations/commercial/qualys/scanner.py +7 -2
  13. regscale/integrations/commercial/sonarcloud.py +110 -71
  14. regscale/integrations/commercial/wizv2/click.py +4 -1
  15. regscale/integrations/commercial/wizv2/data_fetcher.py +401 -0
  16. regscale/integrations/commercial/wizv2/finding_processor.py +295 -0
  17. regscale/integrations/commercial/wizv2/policy_compliance.py +1402 -203
  18. regscale/integrations/commercial/wizv2/policy_compliance_helpers.py +564 -0
  19. regscale/integrations/commercial/wizv2/scanner.py +4 -4
  20. regscale/integrations/compliance_integration.py +212 -60
  21. regscale/integrations/public/fedramp/fedramp_five.py +92 -7
  22. regscale/integrations/scanner_integration.py +27 -4
  23. regscale/models/__init__.py +1 -1
  24. regscale/models/integration_models/cisa_kev_data.json +33 -3
  25. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  26. regscale/models/regscale_models/issue.py +29 -9
  27. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/METADATA +1 -1
  28. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/RECORD +32 -27
  29. tests/regscale/test_authorization.py +0 -65
  30. tests/regscale/test_init.py +0 -96
  31. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/LICENSE +0 -0
  32. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/WHEEL +0 -0
  33. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/entry_points.txt +0 -0
  34. {regscale_cli-6.21.1.0.dist-info → regscale_cli-6.21.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,564 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Helper classes and utilities for Wiz Policy Compliance Integration."""
4
+
5
+ import logging
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from typing import Dict, List, Optional, Any
9
+
10
+ from regscale.core.app.utils.app_utils import get_current_datetime, regscale_string_to_datetime
11
+ from regscale.integrations.scanner_integration import IntegrationFinding
12
+ from regscale.models import regscale_models
13
+
14
+ logger = logging.getLogger("regscale")
15
+
16
+
17
+ @dataclass
18
+ class ControlAssessmentResult:
19
+ """Result of a control assessment operation."""
20
+
21
+ control_id: str
22
+ implementation_id: Optional[int]
23
+ assessment_id: Optional[int]
24
+ result: str
25
+ asset_count: int
26
+ created: bool = False
27
+
28
+
29
+ @dataclass
30
+ class IssueProcessingResult:
31
+ """Result of issue processing operation."""
32
+
33
+ control_id: Optional[str]
34
+ implementation_id: Optional[int]
35
+ assessment_id: Optional[int]
36
+ success: bool
37
+ error_message: Optional[str] = None
38
+
39
+
40
+ class ControlImplementationCache:
41
+ """Cache for control implementation lookups to avoid repeated database queries."""
42
+
43
+ def __init__(self) -> None:
44
+ self._impl_id_by_control: Dict[str, int] = {}
45
+ self._assessment_by_impl_today: Dict[int, regscale_models.Assessment] = {}
46
+ self._security_control_cache: Dict[int, regscale_models.SecurityControl] = {}
47
+ self._loaded = False
48
+
49
+ def get_implementation_id(self, control_id: str) -> Optional[int]:
50
+ """
51
+ Get control implementation ID for normalized control ID.
52
+
53
+ :param control_id: Normalized control ID (e.g., 'AC-2(1)')
54
+ :return: Control implementation ID if found, None otherwise
55
+ """
56
+ return self._impl_id_by_control.get(control_id)
57
+
58
+ def set_implementation_id(self, control_id: str, impl_id: int) -> None:
59
+ """
60
+ Cache control implementation ID.
61
+
62
+ :param control_id: Normalized control ID (e.g., 'AC-2(1)')
63
+ :param impl_id: Control implementation ID to cache
64
+ """
65
+ self._impl_id_by_control[control_id] = impl_id
66
+
67
+ def get_assessment(self, impl_id: int) -> Optional[regscale_models.Assessment]:
68
+ """
69
+ Get assessment for implementation ID.
70
+
71
+ :param impl_id: Control implementation ID
72
+ :return: Cached assessment object if found, None otherwise
73
+ """
74
+ return self._assessment_by_impl_today.get(impl_id)
75
+
76
+ def set_assessment(self, impl_id: int, assessment: regscale_models.Assessment) -> None:
77
+ """
78
+ Cache assessment for implementation.
79
+
80
+ :param impl_id: Control implementation ID
81
+ :param assessment: Assessment object to cache
82
+ """
83
+ self._assessment_by_impl_today[impl_id] = assessment
84
+
85
+ def get_security_control(self, control_id: int) -> Optional[regscale_models.SecurityControl]:
86
+ """
87
+ Get cached security control.
88
+
89
+ :param control_id: Security control ID
90
+ :return: Cached security control object if found, None otherwise
91
+ """
92
+ return self._security_control_cache.get(control_id)
93
+
94
+ def set_security_control(self, control_id: int, security_control: regscale_models.SecurityControl) -> None:
95
+ """
96
+ Cache security control.
97
+
98
+ :param control_id: Security control ID
99
+ :param security_control: Security control object to cache
100
+ """
101
+ self._security_control_cache[control_id] = security_control
102
+
103
+ @property
104
+ def implementation_count(self) -> int:
105
+ """
106
+ Number of cached implementations.
107
+
108
+ :return: Count of cached control implementation mappings
109
+ """
110
+ return len(self._impl_id_by_control)
111
+
112
+ @property
113
+ def assessment_count(self) -> int:
114
+ """
115
+ Number of cached assessments.
116
+
117
+ :return: Count of cached assessment objects
118
+ """
119
+ return len(self._assessment_by_impl_today)
120
+
121
+
122
+ class AssetConsolidator:
123
+ """Handles consolidation of asset identifiers for findings."""
124
+
125
+ MAX_DISPLAY_ASSETS = 10
126
+
127
+ @staticmethod
128
+ def create_consolidated_asset_identifier(asset_mappings: Dict[str, Dict[str, str]]) -> str:
129
+ """
130
+ Create a consolidated asset identifier from asset mappings.
131
+
132
+ :param asset_mappings: Dict mapping resource IDs to asset info
133
+ :return: Consolidated asset identifier string
134
+ """
135
+ if not asset_mappings:
136
+ return ""
137
+
138
+ # Create clean format: "Asset Name (wiz-resource-id)"
139
+ identifiers = []
140
+ for resource_id, info in asset_mappings.items():
141
+ asset_name = info.get("name", resource_id)
142
+ identifier = f"{asset_name} ({resource_id})"
143
+ identifiers.append(identifier)
144
+
145
+ # Sort by asset name for consistency
146
+ identifiers.sort(key=lambda x: x.split(" (")[0])
147
+
148
+ return "\n".join(identifiers)
149
+
150
+ @staticmethod
151
+ def update_finding_description_for_multiple_assets(
152
+ finding: IntegrationFinding, asset_count: int, asset_names: List[str]
153
+ ) -> None:
154
+ """
155
+ Update finding description to indicate multiple affected assets.
156
+
157
+ :param finding: Finding to update
158
+ :param asset_count: Number of affected assets
159
+ :param asset_names: List of asset names
160
+ """
161
+ if asset_count <= 1:
162
+ return
163
+
164
+ display_names = asset_names[: AssetConsolidator.MAX_DISPLAY_ASSETS]
165
+ description_suffix = f"\n\nThis control failure affects {asset_count} assets: {', '.join(display_names)}"
166
+
167
+ if asset_count > AssetConsolidator.MAX_DISPLAY_ASSETS:
168
+ remaining = asset_count - AssetConsolidator.MAX_DISPLAY_ASSETS
169
+ description_suffix += f" (and {remaining} more)"
170
+
171
+ finding.description = f"{finding.description}{description_suffix}"
172
+
173
+
174
+ class IssueFieldSetter:
175
+ """Handles setting control and assessment IDs on issues."""
176
+
177
+ def __init__(self, cache: ControlImplementationCache, plan_id: int, parent_module: str) -> None:
178
+ """
179
+ Initialize the issue field setter.
180
+
181
+ :param cache: Control implementation cache for lookups
182
+ :param plan_id: RegScale security plan ID
183
+ :param parent_module: Parent module name (e.g., 'securityplans')
184
+ """
185
+ self.cache = cache
186
+ self.plan_id = plan_id
187
+ self.parent_module = parent_module
188
+
189
+ def set_control_and_assessment_ids(self, issue: regscale_models.Issue, control_id: str) -> IssueProcessingResult:
190
+ """
191
+ Set control implementation and assessment IDs on an issue.
192
+
193
+ :param issue: Issue to update
194
+ :param control_id: Normalized control ID
195
+ :return: Result of the operation
196
+ """
197
+ try:
198
+ # Get or find control implementation ID
199
+ impl_id = self._get_or_find_implementation_id(control_id)
200
+ if not impl_id:
201
+ return IssueProcessingResult(
202
+ control_id=control_id,
203
+ implementation_id=None,
204
+ assessment_id=None,
205
+ success=False,
206
+ error_message=f"No control implementation found for control '{control_id}'",
207
+ )
208
+
209
+ # Set control implementation ID
210
+ issue.controlId = impl_id
211
+
212
+ # Get or find assessment ID
213
+ assess_id = self._get_or_find_assessment_id(impl_id)
214
+ if assess_id:
215
+ issue.assessmentId = assess_id
216
+
217
+ # Verify the field is set correctly
218
+ if not (hasattr(issue, "assessmentId") and issue.assessmentId == assess_id):
219
+ logger.error(
220
+ f"❌ VERIFICATION FAILED: Expected {assess_id}, got {getattr(issue, 'assessmentId', 'NO_ATTR')}"
221
+ )
222
+ else:
223
+ logger.warning(
224
+ f"⚠️ No assessment found for control implementation {impl_id} (control '{control_id}') - assessmentId will not be set"
225
+ )
226
+
227
+ return IssueProcessingResult(
228
+ control_id=control_id, implementation_id=impl_id, assessment_id=assess_id, success=True
229
+ )
230
+
231
+ except Exception as e:
232
+ logger.error(f"Error setting control and assessment IDs: {e}")
233
+ return IssueProcessingResult(
234
+ control_id=control_id, implementation_id=None, assessment_id=None, success=False, error_message=str(e)
235
+ )
236
+
237
+ def _get_or_find_implementation_id(self, control_id: str) -> Optional[int]:
238
+ """
239
+ Get implementation ID from cache or database.
240
+
241
+ :param control_id: Normalized control ID to search for
242
+ :return: Control implementation ID if found, None otherwise
243
+ """
244
+ # Check cache first
245
+ impl_id = self.cache.get_implementation_id(control_id)
246
+ if impl_id:
247
+ return impl_id
248
+
249
+ # Query database
250
+ impl_id = self._find_implementation_id_in_database(control_id)
251
+ if impl_id:
252
+ self.cache.set_implementation_id(control_id, impl_id)
253
+
254
+ return impl_id
255
+
256
+ def _find_implementation_id_in_database(self, control_id: str) -> Optional[int]:
257
+ """
258
+ Find control implementation ID by querying database.
259
+
260
+ :param control_id: Normalized control ID to search for
261
+ :return: Control implementation ID if found, None otherwise
262
+ """
263
+ try:
264
+ implementations = regscale_models.ControlImplementation.get_all_by_parent(
265
+ parent_id=self.plan_id, parent_module=self.parent_module
266
+ )
267
+
268
+ for impl in implementations:
269
+ if not hasattr(impl, "controlID") or not impl.controlID:
270
+ continue
271
+
272
+ # Check cache for security control
273
+ security_control = self.cache.get_security_control(impl.controlID)
274
+ if not security_control:
275
+ security_control = regscale_models.SecurityControl.get_object(object_id=impl.controlID)
276
+ if security_control:
277
+ self.cache.set_security_control(impl.controlID, security_control)
278
+
279
+ if security_control and hasattr(security_control, "controlId"):
280
+ from regscale.integrations.commercial.wizv2.policy_compliance import WizPolicyComplianceIntegration
281
+
282
+ impl_control_id = WizPolicyComplianceIntegration._normalize_control_id_string(
283
+ security_control.controlId
284
+ )
285
+
286
+ if impl_control_id == control_id:
287
+ logger.debug(f"✓ Found control implementation {impl.id} for control {control_id}")
288
+ return impl.id
289
+
290
+ return None
291
+ except Exception as e:
292
+ logger.error(f"Error finding control implementation for {control_id}: {e}")
293
+ return None
294
+
295
+ def _get_or_find_assessment_id(self, impl_id: int) -> Optional[int]:
296
+ """
297
+ Get assessment ID from cache or database.
298
+
299
+ IMPROVED: More robust assessment lookup with better logging.
300
+
301
+ :param impl_id: Control implementation ID to search for
302
+ :return: Assessment ID if found, None otherwise
303
+ """
304
+ # Check cache first
305
+ assessment = self.cache.get_assessment(impl_id)
306
+ if assessment and hasattr(assessment, "id"):
307
+ return assessment.id
308
+
309
+ # Query database
310
+ assessment = self._find_most_recent_assessment(impl_id)
311
+ if assessment:
312
+ self.cache.set_assessment(impl_id, assessment)
313
+ return assessment.id
314
+
315
+ return None
316
+
317
+ def _find_most_recent_assessment(self, impl_id: int) -> Optional[regscale_models.Assessment]:
318
+ """
319
+ Find most recent assessment for implementation.
320
+
321
+ IMPROVED: Better error handling, logging, and assessment selection logic.
322
+
323
+ :param impl_id: Control implementation ID to search for
324
+ :return: Most recent assessment object if found, None otherwise
325
+ """
326
+ try:
327
+ assessments = regscale_models.Assessment.get_all_by_parent(parent_id=impl_id, parent_module="controls")
328
+
329
+ if not assessments:
330
+ return None
331
+
332
+ # Find today's assessments first
333
+ today = datetime.now().date()
334
+ today_assessments = []
335
+ other_assessments = []
336
+
337
+ for assessment in assessments:
338
+ assessment_date = self._extract_assessment_date(assessment)
339
+ if assessment_date == today:
340
+ today_assessments.append(assessment)
341
+ else:
342
+ other_assessments.append((assessment, assessment_date))
343
+
344
+ # Prefer today's assessments (most recently created)
345
+ if today_assessments:
346
+ best_assessment = max(today_assessments, key=lambda a: getattr(a, "id", 0))
347
+ return best_assessment
348
+
349
+ # Fall back to most recent overall (by date, then by ID)
350
+ if other_assessments:
351
+ best_assessment = max(
352
+ other_assessments, key=lambda x: (x[1] or datetime.min.date(), getattr(x[0], "id", 0))
353
+ )[0]
354
+ return best_assessment
355
+
356
+ return None
357
+ except Exception as e:
358
+ logger.error(f"Error finding assessment for implementation {impl_id}: {e}")
359
+ import traceback
360
+
361
+ return None
362
+
363
+ def _extract_assessment_date(self, assessment) -> Optional[datetime.date]:
364
+ """
365
+ Extract date from assessment object.
366
+
367
+ :param assessment: Assessment object to extract date from
368
+ :return: Extracted date if found, None otherwise
369
+ """
370
+ try:
371
+ date_fields = ["plannedStart", "actualFinish", "plannedFinish", "dateCreated"]
372
+ for field in date_fields:
373
+ if hasattr(assessment, field):
374
+ date_value = getattr(assessment, field)
375
+ if date_value:
376
+ if isinstance(date_value, str):
377
+ return regscale_string_to_datetime(date_value).date()
378
+ elif hasattr(date_value, "date"):
379
+ return date_value.date()
380
+ else:
381
+ return date_value
382
+ return None
383
+ except Exception:
384
+ return None
385
+
386
+
387
+ class ControlAssessmentProcessor:
388
+ """Handles control assessment creation and updates."""
389
+
390
+ def __init__(self, plan_id: int, parent_module: str, scan_date: str, title: str, framework: str) -> None:
391
+ """
392
+ Initialize the control assessment processor.
393
+
394
+ :param plan_id: RegScale security plan ID
395
+ :param parent_module: Parent module name (e.g., 'securityplans')
396
+ :param scan_date: Date of the assessment scan
397
+ :param title: Title for assessments
398
+ :param framework: Framework name (e.g., 'NIST800-53R5')
399
+ """
400
+ self.plan_id = plan_id
401
+ self.parent_module = parent_module
402
+ self.scan_date = scan_date
403
+ self.title = title
404
+ self.framework = framework
405
+ self.cache = ControlImplementationCache()
406
+
407
+ def create_or_update_assessment(
408
+ self,
409
+ implementation: regscale_models.ControlImplementation,
410
+ control_id: str,
411
+ result: str,
412
+ compliance_items: List[Any],
413
+ ) -> Optional[regscale_models.Assessment]:
414
+ """
415
+ Create or update a control assessment.
416
+
417
+ :param implementation: Control implementation
418
+ :param control_id: Control identifier
419
+ :param result: Assessment result ('Pass' or 'Fail')
420
+ :param compliance_items: List of compliance items for this control
421
+ :return: Created or updated assessment
422
+ """
423
+ try:
424
+ # Check for existing assessment today
425
+ existing_assessment = self._find_existing_assessment_for_today(implementation.id)
426
+
427
+ assessment_report = self._create_assessment_report(control_id, result, compliance_items)
428
+
429
+ if existing_assessment:
430
+ # Update existing
431
+ existing_assessment.assessmentResult = result
432
+ existing_assessment.assessmentReport = assessment_report
433
+ existing_assessment.actualFinish = get_current_datetime()
434
+ existing_assessment.dateLastUpdated = get_current_datetime()
435
+ existing_assessment.save()
436
+
437
+ self.cache.set_assessment(implementation.id, existing_assessment)
438
+ logger.info(f"✅ Updated existing assessment {existing_assessment.id} for control {control_id}")
439
+ return existing_assessment
440
+ else:
441
+ # Create new
442
+ assessment = regscale_models.Assessment(
443
+ leadAssessorId=implementation.createdById,
444
+ title=f"{self.title} compliance assessment for {control_id.upper()}",
445
+ assessmentType="Control Testing",
446
+ plannedStart=get_current_datetime(),
447
+ plannedFinish=get_current_datetime(),
448
+ actualFinish=get_current_datetime(),
449
+ assessmentResult=result,
450
+ assessmentReport=assessment_report,
451
+ status="Complete",
452
+ parentId=implementation.id,
453
+ parentModule="controls",
454
+ isPublic=True,
455
+ ).create()
456
+
457
+ self.cache.set_assessment(implementation.id, assessment)
458
+ logger.info(f"✅ Created new assessment {assessment.id} for control {control_id}")
459
+ return assessment
460
+
461
+ except Exception as e:
462
+ logger.error(f"Error creating/updating assessment for control {control_id}: {e}")
463
+ return None
464
+
465
+ def _find_existing_assessment_for_today(self, impl_id: int) -> Optional[regscale_models.Assessment]:
466
+ """
467
+ Find existing assessment for today.
468
+
469
+ :param impl_id: Control implementation ID to search for
470
+ :return: Today's assessment if found, None otherwise
471
+ """
472
+ # Check cache first
473
+ cached = self.cache.get_assessment(impl_id)
474
+ if cached:
475
+ return cached
476
+
477
+ # Query database for today's assessments
478
+ try:
479
+ today = datetime.now().date()
480
+ assessments = regscale_models.Assessment.get_all_by_parent(parent_id=impl_id, parent_module="controls")
481
+
482
+ for assessment in assessments:
483
+ if hasattr(assessment, "actualFinish") and assessment.actualFinish:
484
+ try:
485
+ if isinstance(assessment.actualFinish, str):
486
+ assessment_date = regscale_string_to_datetime(assessment.actualFinish).date()
487
+ elif hasattr(assessment.actualFinish, "date"):
488
+ assessment_date = assessment.actualFinish.date()
489
+ else:
490
+ assessment_date = assessment.actualFinish
491
+
492
+ if assessment_date == today:
493
+ self.cache.set_assessment(impl_id, assessment)
494
+ return assessment
495
+ except Exception:
496
+ continue
497
+
498
+ return None
499
+ except Exception:
500
+ return None
501
+
502
+ def _create_assessment_report(self, control_id: str, result: str, compliance_items: List[Any]) -> str:
503
+ """
504
+ Create HTML assessment report.
505
+
506
+ :param control_id: Control identifier (e.g., 'AC-2(1)')
507
+ :param result: Assessment result ('Pass' or 'Fail')
508
+ :param compliance_items: List of compliance items for this control
509
+ :return: HTML formatted assessment report
510
+ """
511
+ result_color = "#d32f2f" if result == "Fail" else "#2e7d32"
512
+ bg_color = "#ffebee" if result == "Fail" else "#e8f5e8"
513
+
514
+ html_parts = [
515
+ f"""
516
+ <div style="margin-bottom: 20px; padding: 15px; border: 2px solid {result_color};
517
+ border-radius: 5px; background-color: {bg_color};">
518
+ <h3 style="margin: 0 0 10px 0; color: {result_color};">
519
+ {self.title} Compliance Assessment for Control {control_id.upper()}
520
+ </h3>
521
+ <p><strong>Overall Result:</strong>
522
+ <span style="color: {result_color}; font-weight: bold;">{result}</span></p>
523
+ <p><strong>Assessment Date:</strong> {self.scan_date}</p>
524
+ <p><strong>Framework:</strong> {self.framework}</p>
525
+ <p><strong>Total Policy Assessments:</strong> {len(compliance_items)}</p>
526
+ </div>
527
+ """
528
+ ]
529
+
530
+ if compliance_items:
531
+ pass_count = len(
532
+ [
533
+ item
534
+ for item in compliance_items
535
+ if hasattr(item, "compliance_result")
536
+ and item.compliance_result in ["PASS", "PASSED", "pass", "passed"]
537
+ ]
538
+ )
539
+ fail_count = len(compliance_items) - pass_count
540
+
541
+ unique_resources = set()
542
+ unique_policies = set()
543
+
544
+ for item in compliance_items:
545
+ if hasattr(item, "resource_id"):
546
+ unique_resources.add(item.resource_id)
547
+ if hasattr(item, "description") and item.description:
548
+ policy_desc = item.description[:50] + "..." if len(item.description) > 50 else item.description
549
+ unique_policies.add(policy_desc)
550
+
551
+ html_parts.append(
552
+ f"""
553
+ <div style="margin-top: 20px;">
554
+ <h4>Assessment Summary</h4>
555
+ <p><strong>Policy Assessments:</strong> {len(compliance_items)} total</p>
556
+ <p><strong>Unique Policies:</strong> {len(unique_policies)}</p>
557
+ <p><strong>Unique Resources:</strong> {len(unique_resources)}</p>
558
+ <p><strong>Passing:</strong> <span style="color: #2e7d32;">{pass_count}</span></p>
559
+ <p><strong>Failing:</strong> <span style="color: #d32f2f;">{fail_count}</span></p>
560
+ </div>
561
+ """
562
+ )
563
+
564
+ return "\n".join(html_parts)
@@ -1532,10 +1532,10 @@ class WizVulnerabilityIntegration(ScannerIntegration):
1532
1532
  logger.debug("WizVulnerabilityIntegration.get_asset_by_identifier called for %s", identifier)
1533
1533
 
1534
1534
  # Try to provide more diagnostic information
1535
- self._log_missing_asset_diagnostics(identifier)
1536
-
1537
- # Still log the original error for consistency
1538
- self.log_error("1. Asset not found for identifier %s", identifier)
1535
+ if not getattr(self, "suppress_asset_not_found_errors", False):
1536
+ self._log_missing_asset_diagnostics(identifier)
1537
+ # Still log the original error for consistency
1538
+ self.log_error("1. Asset not found for identifier %s", identifier)
1539
1539
 
1540
1540
  return asset
1541
1541