Skip to content

Commit 2992163

Browse files
committed
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
1 parent a5d0efa commit 2992163

5 files changed

Lines changed: 74 additions & 10 deletions

File tree

Backend/api/app/routers/scenarios.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class CorrelationRunRequest(BaseModel):
4141
tag_trace: bool = True
4242
workers: int = 10
4343
overwrite_parser: bool = False
44+
suppress_alerts: bool = False
45+
strip_helios_prefix: bool = False
4446

4547
# Initialize scenario service
4648
scenario_service = ScenarioService()
@@ -321,6 +323,8 @@ async def run_correlation_scenario(
321323
tag_phase=request.tag_phase,
322324
tag_trace=request.tag_trace,
323325
overwrite_parser=request.overwrite_parser,
326+
suppress_alerts=request.suppress_alerts,
327+
strip_helios_prefix=request.strip_helios_prefix,
324328
background_tasks=background_tasks
325329
)
326330

Backend/api/app/services/scenario_service.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ async def start_correlation_scenario(
292292
speed: str = "fast",
293293
dry_run: bool = False,
294294
overwrite_parser: bool = False,
295+
suppress_alerts: bool = False,
296+
strip_helios_prefix: bool = False,
295297
background_tasks=None
296298
) -> str:
297299
"""Start correlation scenario execution with SIEM context and trace ID support"""
@@ -309,6 +311,8 @@ async def start_correlation_scenario(
309311
"tag_phase": tag_phase,
310312
"tag_trace": tag_trace,
311313
"overwrite_parser": overwrite_parser,
314+
"suppress_alerts": suppress_alerts,
315+
"strip_helios_prefix": strip_helios_prefix,
312316
"progress": 0
313317
}
314318

@@ -320,7 +324,9 @@ async def start_correlation_scenario(
320324
siem_context,
321325
trace_id,
322326
tag_phase,
323-
tag_trace
327+
tag_trace,
328+
suppress_alerts,
329+
strip_helios_prefix
324330
)
325331

326332
return execution_id
@@ -332,7 +338,9 @@ async def _execute_correlation_scenario(
332338
siem_context: Dict[str, Any],
333339
trace_id: Optional[str] = None,
334340
tag_phase: bool = True,
335-
tag_trace: bool = True
341+
tag_trace: bool = True,
342+
suppress_alerts: bool = False,
343+
strip_helios_prefix: bool = False
336344
):
337345
"""Execute correlation scenario with SIEM context and trace ID support"""
338346
import sys
@@ -357,7 +365,11 @@ async def _execute_correlation_scenario(
357365

358366
# Import and run the scenario
359367
module = __import__(scenario_id)
360-
scenario_result = module.generate_apollo_ransomware_scenario(siem_context=siem_context)
368+
scenario_result = module.generate_apollo_ransomware_scenario(
369+
siem_context=siem_context,
370+
suppress_alerts=suppress_alerts,
371+
strip_helios_prefix=strip_helios_prefix,
372+
)
361373

362374
# Update execution status
363375
if execution_id in self.running_scenarios:

Backend/scenarios/apollo_ransomware_scenario.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ def load_alert_template(template_id: str) -> Optional[Dict]:
306306
def 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
@@ -378,6 +379,12 @@ def send_phase_alert(
378379
else:
379380
alert[key] = value
380381

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+
381388
# Send alert via UAM ingest API
382389
try:
383390
ingest_url = uam_config['uam_ingest_url'].rstrip('/') + '/v1/alerts'
@@ -663,15 +670,26 @@ def generate_m365_sharepoint_exfil(base_time: datetime) -> List[Dict]:
663670
return events
664671

665672

666-
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:
667678
"""Generate the complete Apollo ransomware scenario (Proofpoint + M365 only)
668679
669680
Args:
670681
siem_context: Optional dict with SIEM query results for timestamp correlation.
671682
Expected format: {"results": [...], "anchors": {...}}
672683
If provided, timestamps are calculated relative to existing EDR/WEL data.
673684
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.
674687
"""
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'
675693

676694
# Determine base time based on SIEM context or fallback
677695
use_correlation = False
@@ -714,7 +732,7 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
714732
print("=" * 80 + "\n")
715733

716734
# Initialize alert detonation from env vars
717-
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'
718736
uam_config = None
719737

720738
if alerts_enabled:
@@ -779,6 +797,8 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
779797
print("\n🚨 ALERT DETONATION ENABLED")
780798
print(f" UAM Ingest: {uam_ingest_url}")
781799
print(f" Account ID: {uam_account_id}")
800+
if strip_helios_prefix:
801+
print(f" Strip HELIOS prefix: Yes")
782802
print("=" * 80)
783803
else:
784804
print("⚠️ SCENARIO_ALERTS_ENABLED=true but UAM credentials missing")
@@ -821,13 +841,13 @@ def generate_apollo_ransomware_scenario(siem_context: Optional[Dict] = None) ->
821841
# Send corresponding alert if enabled and phase has alert mapping
822842
if alerts_enabled and phase_name in ALERT_PHASE_MAPPING:
823843
print(f" 📤 Sending alert for {phase_name}...", end=" ")
824-
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)
825845
print(f"{'✓' if success else '✗'}")
826846

827847
# Send RDP alert after data exfiltration phase
828848
if alerts_enabled and phase_name == "📤 PHASE 4: Data Exfiltration":
829849
print(f" 📤 Sending RDP download alert...", end=" ")
830-
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)
831851
print(f"{'✓' if success else '✗'}")
832852

833853
# 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) ->
841861
]
842862
for alert_key, alert_desc in wel_alerts:
843863
print(f" 📤 {alert_desc}...", end=" ")
844-
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)
845865
print(f"{'✓' if success else '✗'}")
846866

847867
all_events.sort(key=lambda x: x["timestamp"])

Frontend/log_generator_ui.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,8 @@ def run_correlation_scenario():
612612
trace_id = (data.get('trace_id') or '').strip()
613613
local_token = data.get('hec_token')
614614
overwrite_parser = data.get('overwrite_parser', False)
615+
suppress_alerts = data.get('suppress_alerts', False)
616+
strip_helios_prefix = data.get('strip_helios_prefix', False)
615617

616618
if not scenario_id:
617619
return jsonify({'error': 'scenario_id is required'}), 400
@@ -815,7 +817,14 @@ def generate_and_stream():
815817
yield "INFO: ⚠️ S1 asset linking disabled (no S1 API token on destination)\n"
816818
except Exception as s1e:
817819
logger.warning(f"Could not resolve S1 API token: {s1e}")
818-
yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n"
820+
if suppress_alerts:
821+
env['SCENARIO_SUPPRESS_ALERTS'] = 'true'
822+
yield "INFO: 🔇 Alert detonation suppressed by user\n"
823+
else:
824+
yield "INFO: 🚨 Alert detonation enabled (UAM credentials found)\n"
825+
if strip_helios_prefix:
826+
env['SCENARIO_STRIP_HELIOS_PREFIX'] = 'true'
827+
yield "INFO: ✂️ HELIOS prefix will be stripped from alert titles\n"
819828
else:
820829
yield "INFO: ⚠️ Alert detonation disabled (no UAM credentials on destination)\n"
821830

Frontend/templates/log_generator.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,21 @@ <h4 class="text-sm font-semibold text-[#c8b3e8] mb-3">Discovered Time Anchors</h
795795
<p class="text-xs text-[#b8a0d2]">If checked, will overwrite existing parsers in the destination SIEM instead of skipping them. Use with caution.</p>
796796
</div>
797797
</details>
798+
<details>
799+
<summary class="cursor-pointer text-sm font-semibold text-[#c8b3e8]">Alert Options</summary>
800+
<div class="mt-3 space-y-2 text-sm">
801+
<label class="flex items-center gap-2">
802+
<input type="checkbox" id="correlation-suppress-alerts">
803+
<span class="text-[#f3e8ff]">Suppress alerts</span>
804+
</label>
805+
<p class="text-xs text-[#b8a0d2]">Skip sending UAM alerts even if credentials are configured on the destination.</p>
806+
<label class="flex items-center gap-2">
807+
<input type="checkbox" id="correlation-strip-helios-prefix">
808+
<span class="text-[#f3e8ff]">Strip <code>HELIOS -</code> prefix from alert titles</span>
809+
</label>
810+
<p class="text-xs text-[#b8a0d2]">Remove the "HELIOS - " prefix from alert titles before sending to S1.</p>
811+
</div>
812+
</details>
798813

799814
<div class="flex gap-3">
800815
<button type="button" id="run-correlation-scenario-btn" class="btn flex-1" disabled>
@@ -3487,6 +3502,8 @@ <h3 class="text-xl font-bold text-[#f3e8ff]">Select Parser Version</h3>
34873502
const tagPhase = document.getElementById('correlation-tag-phase').checked;
34883503
const tagTrace = document.getElementById('correlation-tag-trace').checked;
34893504
const overwriteParser = document.getElementById('correlation-overwrite-parser').checked;
3505+
const suppressAlerts = document.getElementById('correlation-suppress-alerts').checked;
3506+
const stripHeliosPrefix = document.getElementById('correlation-strip-helios-prefix').checked;
34903507
let traceId = correlationTraceIdInput.value.trim();
34913508

34923509
if (tagTrace && !traceId) {
@@ -3519,6 +3536,8 @@ <h3 class="text-xl font-bold text-[#f3e8ff]">Select Parser Version</h3>
35193536
tag_trace: tagTrace,
35203537
trace_id: traceId,
35213538
overwrite_parser: overwriteParser,
3539+
suppress_alerts: suppressAlerts,
3540+
strip_helios_prefix: stripHeliosPrefix,
35223541
hec_token: localToken
35233542
})
35243543
});

0 commit comments

Comments
 (0)