From a5d0efa815b5da648e6628c3a8c8dff7569c848b Mon Sep 17 00:00:00 2001 From: jmorascalyr <42879226+jmorascalyr@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:01:00 -0700 Subject: [PATCH 1/2] fix: Use fresh UUID for email alerts to prevent UAM silent drops and update classification ID - Changed email alert resource UID from shared GUID to fresh UUID (uuid.uuid4()) to prevent S1 UAM from silently dropping site-scoped alerts with static resource UIDs - Updated proofpoint_email_alert.json s1_classification_id from 1 to 28 - Removed email_asset_uid caching logic that was causing alert deduplication issues --- .../app/alerts/templates/proofpoint_email_alert.json | 2 +- Backend/scenarios/apollo_ransomware_scenario.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Backend/api/app/alerts/templates/proofpoint_email_alert.json b/Backend/api/app/alerts/templates/proofpoint_email_alert.json index 37ec97b..6e69bb4 100644 --- a/Backend/api/app/alerts/templates/proofpoint_email_alert.json +++ b/Backend/api/app/alerts/templates/proofpoint_email_alert.json @@ -36,5 +36,5 @@ "attack_surface_ids": [1], "severity_id": 4, "state_id": 1, - "s1_classification_id": 1 + "s1_classification_id": 28 } diff --git a/Backend/scenarios/apollo_ransomware_scenario.py b/Backend/scenarios/apollo_ransomware_scenario.py index 3c7a8ca..5ad25dd 100644 --- a/Backend/scenarios/apollo_ransomware_scenario.py +++ b/Backend/scenarios/apollo_ransomware_scenario.py @@ -341,16 +341,12 @@ def send_phase_alert( alert["metadata"]["logged_time"] = time_ms alert["metadata"]["modified_time"] = time_ms - # Set resource - use XDR Asset ID for endpoint alerts, shared GUID for email/user alerts + # Set resource - use XDR Asset ID for endpoint alerts, fresh UUID for email/user alerts target_machine = mapping.get("target_machine", "bridge") if target_machine == "email": - # Proofpoint/M365 alerts link to the user email with a consistent GUID - email_asset_uid = uam_config.get('email_asset_uid') - if not email_asset_uid: - email_asset_uid = str(uuid.uuid5(uuid.NAMESPACE_DNS, VICTIM_PROFILE["email"])) - uam_config['email_asset_uid'] = email_asset_uid + # Fresh UUID each time ā S1 UAM silently drops site-scoped alerts with static resource UIDs alert["resources"] = [{ - "uid": email_asset_uid, + "uid": str(uuid.uuid4()), "name": VICTIM_PROFILE["email"] }] elif target_machine == "enterprise": From 29921639eeb9a92fb80092ce21a0fcbfa68d3df6 Mon Sep 17 00:00:00 2001 From: jmorascalyr <42879226+jmorascalyr@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:31:12 -0700 Subject: [PATCH 2/2] feat: Add alert suppression and HELIOS prefix stripping options to correlation scenario execution - Added suppress_alerts and strip_helios_prefix boolean flags to CorrelationRunRequest model - Updated start_correlation_scenario() and _execute_correlation_scenario() to accept and pass new alert control flags - Modified send_phase_alert() to conditionally strip "HELIOS - " prefix from alert titles when strip_helios_prefix=True - Added environment variable fallbacks (SCENARIO_SUPPRESS_ALERTS, SCENARIO_STRIP_HEL --- Backend/api/app/routers/scenarios.py | 4 +++ Backend/api/app/services/scenario_service.py | 18 +++++++++-- .../scenarios/apollo_ransomware_scenario.py | 32 +++++++++++++++---- Frontend/log_generator_ui.py | 11 ++++++- Frontend/templates/log_generator.html | 19 +++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/Backend/api/app/routers/scenarios.py b/Backend/api/app/routers/scenarios.py index c0d118c..62b36ac 100644 --- a/Backend/api/app/routers/scenarios.py +++ b/Backend/api/app/routers/scenarios.py @@ -41,6 +41,8 @@ class CorrelationRunRequest(BaseModel): tag_trace: bool = True workers: int = 10 overwrite_parser: bool = False + suppress_alerts: bool = False + strip_helios_prefix: bool = False # Initialize scenario service scenario_service = ScenarioService() @@ -321,6 +323,8 @@ async def run_correlation_scenario( tag_phase=request.tag_phase, tag_trace=request.tag_trace, overwrite_parser=request.overwrite_parser, + suppress_alerts=request.suppress_alerts, + strip_helios_prefix=request.strip_helios_prefix, background_tasks=background_tasks ) diff --git a/Backend/api/app/services/scenario_service.py b/Backend/api/app/services/scenario_service.py index 339fdf5..fc21e8f 100644 --- a/Backend/api/app/services/scenario_service.py +++ b/Backend/api/app/services/scenario_service.py @@ -292,6 +292,8 @@ async def start_correlation_scenario( speed: str = "fast", dry_run: bool = False, overwrite_parser: bool = False, + suppress_alerts: bool = False, + strip_helios_prefix: bool = False, background_tasks=None ) -> str: """Start correlation scenario execution with SIEM context and trace ID support""" @@ -309,6 +311,8 @@ async def start_correlation_scenario( "tag_phase": tag_phase, "tag_trace": tag_trace, "overwrite_parser": overwrite_parser, + "suppress_alerts": suppress_alerts, + "strip_helios_prefix": strip_helios_prefix, "progress": 0 } @@ -320,7 +324,9 @@ async def start_correlation_scenario( siem_context, trace_id, tag_phase, - tag_trace + tag_trace, + suppress_alerts, + strip_helios_prefix ) return execution_id @@ -332,7 +338,9 @@ async def _execute_correlation_scenario( siem_context: Dict[str, Any], trace_id: Optional[str] = None, tag_phase: bool = True, - tag_trace: bool = True + tag_trace: bool = True, + suppress_alerts: bool = False, + strip_helios_prefix: bool = False ): """Execute correlation scenario with SIEM context and trace ID support""" import sys @@ -357,7 +365,11 @@ async def _execute_correlation_scenario( # Import and run the scenario module = __import__(scenario_id) - scenario_result = module.generate_apollo_ransomware_scenario(siem_context=siem_context) + scenario_result = module.generate_apollo_ransomware_scenario( + siem_context=siem_context, + suppress_alerts=suppress_alerts, + strip_helios_prefix=strip_helios_prefix, + ) # Update execution status if execution_id in self.running_scenarios: diff --git a/Backend/scenarios/apollo_ransomware_scenario.py b/Backend/scenarios/apollo_ransomware_scenario.py index 5ad25dd..e297875 100644 --- a/Backend/scenarios/apollo_ransomware_scenario.py +++ b/Backend/scenarios/apollo_ransomware_scenario.py @@ -306,7 +306,8 @@ def load_alert_template(template_id: str) -> Optional[Dict]: def send_phase_alert( phase_name: str, base_time: datetime, - uam_config: dict + uam_config: dict, + strip_helios_prefix: bool = False ) -> bool: """Send alert for a specific phase with correct timing. @@ -378,6 +379,12 @@ def send_phase_alert( else: alert[key] = value + # Strip "HELIOS - " prefix from alert title if requested + if strip_helios_prefix: + title = alert.get("finding_info", {}).get("title", "") + if title.startswith("HELIOS - "): + alert["finding_info"]["title"] = title[len("HELIOS - "):] + # Send alert via UAM ingest API try: ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts' @@ -663,7 +670,11 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]: return events -def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> Dict: +def generate_apollo_ransomware_scenario( + siem_context: Optional[Dict] = None, + suppress_alerts: Optional[bool] = None, + strip_helios_prefix: Optional[bool] = None, +) -> Dict: """Generate the complete Apollo ransomware scenario (Proofpoint + M365 only) Args: @@ -671,7 +682,14 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> Expected format: {"results": [...], "anchors": {...}} If provided, timestamps are calculated relative to existing EDR/WEL data. If None, falls back to offset from current time. + suppress_alerts: If True, skip sending UAM alerts. Env fallback: SCENARIO_SUPPRESS_ALERTS. + strip_helios_prefix: If True, remove "HELIOS - " prefix from alert titles. Env fallback: SCENARIO_STRIP_HELIOS_PREFIX. """ + # Resolve options from args or env vars + if suppress_alerts is None: + suppress_alerts = os.getenv('SCENARIO_SUPPRESS_ALERTS', 'false').lower() == 'true' + if strip_helios_prefix is None: + strip_helios_prefix = os.getenv('SCENARIO_STRIP_HELIOS_PREFIX', 'false').lower() == 'true' # Determine base time based on SIEM context or fallback use_correlation = False @@ -714,7 +732,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> print("=" * 80 + "\n") # Initialize alert detonation from env vars - alerts_enabled = os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true' + alerts_enabled = (not suppress_alerts) and os.getenv('SCENARIO_ALERTS_ENABLED', 'false').lower() == 'true' uam_config = None if alerts_enabled: @@ -779,6 +797,8 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> print("\nšØ ALERT DETONATION ENABLED") print(f" UAM Ingest: {uam_ingest_url}") print(f" Account ID: {uam_account_id}") + if strip_helios_prefix: + print(f" Strip HELIOS prefix: Yes") print("=" * 80) else: print("ā ļø SCENARIO_ALERTS_ENABLED=true but UAM credentials missing") @@ -821,13 +841,13 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> # Send corresponding alert if enabled and phase has alert mapping if alerts_enabled and phase_name in ALERT_PHASE_MAPPING: print(f" š¤ Sending alert for {phase_name}...", end=" ") - success = send_phase_alert(phase_name, phase_base_time, uam_config) + success = send_phase_alert(phase_name, phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix) print(f"{'ā' if success else 'ā'}") # Send RDP alert after data exfiltration phase if alerts_enabled and phase_name == "š¤ PHASE 4: Data Exfiltration": print(f" š¤ Sending RDP download alert...", end=" ") - success = send_phase_alert("rdp_download", phase_base_time, uam_config) + success = send_phase_alert("rdp_download", phase_base_time, uam_config, strip_helios_prefix=strip_helios_prefix) print(f"{'ā' if success else 'ā'}") # Send standalone WEL alerts (not tied to a specific event generation phase) @@ -841,7 +861,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) -> ] for alert_key, alert_desc in wel_alerts: print(f" š¤ {alert_desc}...", end=" ") - success = send_phase_alert(alert_key, base_time, uam_config) + success = send_phase_alert(alert_key, base_time, uam_config, strip_helios_prefix=strip_helios_prefix) print(f"{'ā' if success else 'ā'}") all_events.sort(key=lambda x: x["timestamp"]) diff --git a/Frontend/log_generator_ui.py b/Frontend/log_generator_ui.py index 4c645ef..1a10e63 100644 --- a/Frontend/log_generator_ui.py +++ b/Frontend/log_generator_ui.py @@ -612,6 +612,8 @@ def run_correlation_scenario(): trace_id = (data.get('trace_id') or '').strip() local_token = data.get('hec_token') overwrite_parser = data.get('overwrite_parser', False) + suppress_alerts = data.get('suppress_alerts', False) + strip_helios_prefix = data.get('strip_helios_prefix', False) if not scenario_id: return jsonify({'error': 'scenario_id is required'}), 400 @@ -815,7 +817,14 @@ def generate_and_stream(): yield "INFO: ā ļø S1 asset linking disabled (no S1 API token on destination)\n" except Exception as s1e: logger.warning(f"Could not resolve S1 API token: {s1e}") - yield "INFO: šØ Alert detonation enabled (UAM credentials found)\n" + if suppress_alerts: + env['SCENARIO_SUPPRESS_ALERTS'] = 'true' + yield "INFO: š Alert detonation suppressed by user\n" + else: + yield "INFO: šØ Alert detonation enabled (UAM credentials found)\n" + if strip_helios_prefix: + env['SCENARIO_STRIP_HELIOS_PREFIX'] = 'true' + yield "INFO: āļø HELIOS prefix will be stripped from alert titles\n" else: yield "INFO: ā ļø Alert detonation disabled (no UAM credentials on destination)\n" diff --git a/Frontend/templates/log_generator.html b/Frontend/templates/log_generator.html index 322210f..de592ca 100644 --- a/Frontend/templates/log_generator.html +++ b/Frontend/templates/log_generator.html @@ -795,6 +795,21 @@
Skip sending UAM alerts even if credentials are configured on the destination.
+ +Remove the "HELIOS - " prefix from alert titles before sending to S1.
+