@@ -306,7 +306,8 @@ def load_alert_template(template_id: str) -> Optional[Dict]:
306306def send_phase_alert (
307307 phase_name : str ,
308308 base_time : datetime ,
309- uam_config : dict
309+ uam_config : dict ,
310+ strip_helios_prefix : bool = False
310311) -> bool :
311312 """Send alert for a specific phase with correct timing.
312313
@@ -341,16 +342,12 @@ def send_phase_alert(
341342 alert ["metadata" ]["logged_time" ] = time_ms
342343 alert ["metadata" ]["modified_time" ] = time_ms
343344
344- # Set resource - use XDR Asset ID for endpoint alerts, shared GUID for email/user alerts
345+ # Set resource - use XDR Asset ID for endpoint alerts, fresh UUID for email/user alerts
345346 target_machine = mapping .get ("target_machine" , "bridge" )
346347 if target_machine == "email" :
347- # Proofpoint/M365 alerts link to the user email with a consistent GUID
348- email_asset_uid = uam_config .get ('email_asset_uid' )
349- if not email_asset_uid :
350- email_asset_uid = str (uuid .uuid5 (uuid .NAMESPACE_DNS , VICTIM_PROFILE ["email" ]))
351- uam_config ['email_asset_uid' ] = email_asset_uid
348+ # Fresh UUID each time — S1 UAM silently drops site-scoped alerts with static resource UIDs
352349 alert ["resources" ] = [{
353- "uid" : email_asset_uid ,
350+ "uid" : str ( uuid . uuid4 ()) ,
354351 "name" : VICTIM_PROFILE ["email" ]
355352 }]
356353 elif target_machine == "enterprise" :
@@ -382,6 +379,12 @@ def send_phase_alert(
382379 else :
383380 alert [key ] = value
384381
382+ # Strip "HELIOS - " prefix from alert title if requested
383+ if strip_helios_prefix :
384+ title = alert .get ("finding_info" , {}).get ("title" , "" )
385+ if title .startswith ("HELIOS - " ):
386+ alert ["finding_info" ]["title" ] = title [len ("HELIOS - " ):]
387+
385388 # Send alert via UAM ingest API
386389 try :
387390 ingest_url = uam_config ['uam_ingest_url' ].rstrip ('/' ) + '/v1/alerts'
@@ -667,15 +670,26 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]:
667670 return events
668671
669672
670- def generate_apollo_ransomware_scenario (siem_context : Optional [Dict ] = None ) -> Dict :
673+ def generate_apollo_ransomware_scenario (
674+ siem_context : Optional [Dict ] = None ,
675+ suppress_alerts : Optional [bool ] = None ,
676+ strip_helios_prefix : Optional [bool ] = None ,
677+ ) -> Dict :
671678 """Generate the complete Apollo ransomware scenario (Proofpoint + M365 only)
672679
673680 Args:
674681 siem_context: Optional dict with SIEM query results for timestamp correlation.
675682 Expected format: {"results": [...], "anchors": {...}}
676683 If provided, timestamps are calculated relative to existing EDR/WEL data.
677684 If None, falls back to offset from current time.
685+ suppress_alerts: If True, skip sending UAM alerts. Env fallback: SCENARIO_SUPPRESS_ALERTS.
686+ strip_helios_prefix: If True, remove "HELIOS - " prefix from alert titles. Env fallback: SCENARIO_STRIP_HELIOS_PREFIX.
678687 """
688+ # Resolve options from args or env vars
689+ if suppress_alerts is None :
690+ suppress_alerts = os .getenv ('SCENARIO_SUPPRESS_ALERTS' , 'false' ).lower () == 'true'
691+ if strip_helios_prefix is None :
692+ strip_helios_prefix = os .getenv ('SCENARIO_STRIP_HELIOS_PREFIX' , 'false' ).lower () == 'true'
679693
680694 # Determine base time based on SIEM context or fallback
681695 use_correlation = False
@@ -718,7 +732,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
718732 print ("=" * 80 + "\n " )
719733
720734 # Initialize alert detonation from env vars
721- alerts_enabled = os .getenv ('SCENARIO_ALERTS_ENABLED' , 'false' ).lower () == 'true'
735+ alerts_enabled = ( not suppress_alerts ) and os .getenv ('SCENARIO_ALERTS_ENABLED' , 'false' ).lower () == 'true'
722736 uam_config = None
723737
724738 if alerts_enabled :
@@ -783,6 +797,8 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
783797 print ("\n 🚨 ALERT DETONATION ENABLED" )
784798 print (f" UAM Ingest: { uam_ingest_url } " )
785799 print (f" Account ID: { uam_account_id } " )
800+ if strip_helios_prefix :
801+ print (f" Strip HELIOS prefix: Yes" )
786802 print ("=" * 80 )
787803 else :
788804 print ("⚠️ SCENARIO_ALERTS_ENABLED=true but UAM credentials missing" )
@@ -825,13 +841,13 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
825841 # Send corresponding alert if enabled and phase has alert mapping
826842 if alerts_enabled and phase_name in ALERT_PHASE_MAPPING :
827843 print (f" 📤 Sending alert for { phase_name } ..." , end = " " )
828- success = send_phase_alert (phase_name , phase_base_time , uam_config )
844+ success = send_phase_alert (phase_name , phase_base_time , uam_config , strip_helios_prefix = strip_helios_prefix )
829845 print (f"{ '✓' if success else '✗' } " )
830846
831847 # Send RDP alert after data exfiltration phase
832848 if alerts_enabled and phase_name == "📤 PHASE 4: Data Exfiltration" :
833849 print (f" 📤 Sending RDP download alert..." , end = " " )
834- success = send_phase_alert ("rdp_download" , phase_base_time , uam_config )
850+ success = send_phase_alert ("rdp_download" , phase_base_time , uam_config , strip_helios_prefix = strip_helios_prefix )
835851 print (f"{ '✓' if success else '✗' } " )
836852
837853 # Send standalone WEL alerts (not tied to a specific event generation phase)
@@ -845,7 +861,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
845861 ]
846862 for alert_key , alert_desc in wel_alerts :
847863 print (f" 📤 { alert_desc } ..." , end = " " )
848- success = send_phase_alert (alert_key , base_time , uam_config )
864+ success = send_phase_alert (alert_key , base_time , uam_config , strip_helios_prefix = strip_helios_prefix )
849865 print (f"{ '✓' if success else '✗' } " )
850866
851867 all_events .sort (key = lambda x : x ["timestamp" ])
0 commit comments