This document provides comprehensive documentation for the RADAR Active Response system, which implements automated threat detection and response capabilities. The Active Response module is located at /radar/scenarios/active_responses/radar_ar.py and follows a structured workflow to identify, assess, and respond to security events.
Production Scenario Support: The active response script’s scenario registry (
Registryclass inradar_ar.py) currently supports three production scenarios:geoip_detection,suspicious_login, andlog_volume. Archived demo scenarios (insider_threat,ddos_detection,malware_communication) in/radar/archives/use different active response implementations and require adaptation for production use.
The RADAR Active Response system follows a structured execution pipeline implemented in the RadarActiveResponse.run() method:
Exit codes:
0: Success (scenario matched and processed)1: No alert received or no scenario match2: Critical unhandled exceptionThe script integrates with Wazuh’s Active Response framework by:
/var/ossec/logs/active-responses.logExpected JSON structure from stdin:
{
"id": "1234567890.123456",
"timestamp": "2026-02-06T10:15:30.123+00:00",
"rule": {
"id": "210012",
"level": 10,
"description": "Multiple failed SSH login attempts",
"groups": ["authentication_failed", "sshd"]
},
"agent": {
"id": "001",
"name": "web-server-01"
},
"data": {
"srcip": "203.0.113.42",
"dstuser": "admin",
"anomaly_grade": 0.85,
"anomaly_confidence": 0.92
},
"full_log": "..."
}
When mitigations are enabled, the script uses WazuhApiClient to dispatch active response commands via Wazuh API:
PUT /active-response?agents_list={agent_id}&wait_for_complete=true
Authorization: Bearer {token}
{
"command": "firewall_drop",
"arguments": ["203.0.113.42"],
"alert": {"data": {...}}
}
We expect:
rule.id, rule.level, rule.groupstimestampagent.id, agent.namefull_logWe maintain ar.yaml at /radar/scenarios/active_responses/ar.yaml:
rule.id → scenariorule.id → detection_type (signature vs ad)The ScenarioIdentifier class iterates through configured scenarios and returns:
{
"name": "geoip_detection",
"detection": "signature",
"alert": {...},
"config": {...} # Scenario-specific configuration from ar.yaml
}
If no scenario matches the rule ID, the script logs a warning and exits with code 0 (no-op).
The system uses a strategy pattern with scenario-specific classes that extend BaseScenario. Each scenario can override behavior for time window resolution, agent name resolution, and AD score extraction.
Time Window Resolution:
delta_ad_minutes (default: 10) from configurationdelta_signature_minutes (default: 1) from configuration[alert_timestamp - delta, alert_timestamp]Effective Agent Resolution:
None (query all agents)agent.nameAD Score Extraction:
anomaly_grade and anomaly_confidence from alert.data(grade_float, confidence_float) or logs error if missing/invalidInherits all default behavior from BaseScenario. No custom overrides.
Inherits all default behavior from BaseScenario. No custom overrides.
Custom Time Window Resolution:
period_start and period_end from alert.dataBaseScenario delta-based calculationCustom Effective Agent Resolution:
entity_keyword from alert.data (SONAR detector’s entity field)BaseScenario behaviorThis allows log volume anomalies to use the exact detection period and target agent from SONAR’s alert metadata.
We query OpenSearch around the triggering event time:
T - Δ_before to TWe extract all events per entity within T - Δ_before and T, where Δ_before are detection interval for AD. While for signature-based, it is a configurable value.
For AD-based detection,
Δfollows the detector interval. For signature-based detection,Δis minute-based and configurable via YAML (default: 1 minute).
We identify IOCs from:
Typical IOCs:
ip: src_ip, dst_ipuser: username, account iddomain: hostnames, urlshash: file hashesservice: service nameasn, country, agentCurrent Implementation Status: The active response script currently uses a mock CTI client (
SatrapClientMockin/radar/scenarios/active_responses/radar_ar.py). Full SATRAP-DL DECIPHER subsystem integration is planned for a future release. The risk engine formulas and CTI score calculations described below are implemented and functional, using mock data for testing and demonstration purposes.
We define a minimal normalized CTI contract:
cti_threat_score in [0..1]cti_confidence in [0..1]labels: botnet, scanner, ransomware, vpn_exit_nodeIf SATRAP does not provide numeric score:
If SATRAP is unreachable:
cti_threat_score = 0 and cti_confidence = 0The risk engine combines anomaly detection, signature-based detection, and CTI indicators into a normalized risk score ∈ [0, 1]. See radar-risk-math.md for complete mathematical specification.
where A = G×C (anomaly intensity), S = L×I (signature risk), T = CTI score, and weights satisfy w_A + w_S + w_T = 1.
Scenario weight configurations (from ar.yaml):
The likelihood value can be configured in two ways:
Scalar mode (single value for all rules in scenario):
signature_likelihood: 0.8
List mode (rule-based mapping for fine-grained control):
signature_likelihood:
- rule_id: [210012, 210013]
weight: 0.5
- rule_id: [210020, 210021]
weight: 0.7
If no match is found in list mode, likelihood defaults to 0.0.
CTI indicators are aggregated using the formula T = 1 - ∏(1 - w_i) with hardcoded weights (IP: 0.6, Domain: 0.4, Hash: 0.7, User: 0.5). See radar-risk-math.md for detailed specification and examples.
The risk engine returns detailed component information for explainability:
{
"risk_score": 0.4795,
"tier": 2,
"components": {
"anomaly_component": 0.1835, # w_A × A
"anomaly_intensity_A": 0.4588, # G × C
"signature_component": 0.144, # w_S × S
"signature_risk_S": 0.36, # L × I
"cti_component": 0.152, # w_T × T
"cti_score_T": 0.76,
# ... plus individual G, C, L, I values
}
}
For complete calculation examples, see radar-risk-math.md.
The system implements a three-tier response framework. Default boundaries (configurable in ar.yaml):
Threshold: 0.0 ≤ R < 0.33 (risk score below tier1_max)
Mandatory Actions:
Rationale: Low-risk events require awareness but do not warrant automated remediation.
Threshold: 0.33 ≤ R < 0.66 (risk score between tier1_max and tier2_max)
Mandatory Actions:
Optional Mitigations:
firewall-drop (with timeout)lock_user_linux and/or firewall-drop depending on evidenceThreshold: 0.66 ≤ R ≤ 1.0 (risk score at or above tier2_max)
Mandatory Actions:
Mitigation Actions:
After risk calculation, the ActionPlanner determines which actions to execute based on tier and configuration flags.
ActionPlanner.plan())Input: Decision dictionary containing scenario, risk, context, and CTI data
Output: Planned actions dictionary:
{
"notify_email": bool, # Always True (all tiers)
"create_flowintel_case": bool, # True for tier 2 and 3
"mitigations": [str] # List of mitigation commands
}
Decision Tree:
allow_mitigation = true in scenario configuration, ANDmitigations list is configured in scenario YAMLExample Configuration (ar.yaml):
scenarios:
geoip_detection:
allow_mitigation: true
mitigations:
- firewall_drop
# ... other config
Safety: The planner respects the allow_mitigation flag (default: false) to prevent unintended automated actions in production.
The ActionExecutor orchestrates the execution of planned actions and handles failures gracefully.
ActionExecutor.execute())FlowintelClient.create_case()"RADAR {scenario_name} {vm_name} {YYYYMMDD} {HHMMSS}"mitigations list:
_resolve_agent_id())Strategy:
effective_agent from context (if available)
/agents?search={effective_agent}alert.agent.id if API lookup fails or effective_agent is NoneNone if both methods fail (mitigation skipped)This allows targeting the correct agent even when SONAR’s detected entity differs from the alert’s origin agent.
_build_args())Each mitigation command requires specific arguments extracted from IOCs:
| Command | Argument Source | Example |
|---|---|---|
firewall_drop |
First IP from iocs.ip |
["203.0.113.42"] |
lock_user_linux |
First user from iocs.user |
["admin"] |
terminate_service |
First service from iocs.service |
["apache2"] |
Safety: If required IOCs are missing, arguments return None and mitigation is skipped with error log.
API Call:
PUT /active-response?agents_list={agent_id}&wait_for_complete=true
Content-Type: application/json
Authorization: Bearer {token}
{
"command": "firewall_drop",
"arguments": ["203.0.113.42"],
"alert": {
"data": {...} # Original alert data
}
}
Response Handling:
WAZUH_TIMEOUT_SEC (default: 30s){
"flowintel_case": {
"ok": True,
"case_id": "12345",
"title": "RADAR geoip_detection web-server-01 20260206 101530",
"raw": {...}
},
"mitigations": [
{
"command": "firewall_drop",
"agent_id": "001",
"args": ["203.0.113.42"],
"result": {...} # Wazuh API response
},
{
"command": "lock_user_linux",
"agent_id": "001",
"args": ["admin"],
"error": "Wazuh API timeout"
}
]
}
The DecisionId.build() method creates a unique, deterministic identifier for each active response decision using SHA256 hashing.
{
"alert_id": "1234567890.123456",
"timestamp": "2026-02-06T10:15:30.123+00:00",
"rule_id": "210012",
"agent_id": "001",
"scenario": "geoip_detection",
"detection": "signature",
"window": {
"start": "2026-02-06T10:14:30.123+00:00",
"end": "2026-02-06T10:15:30.123+00:00"
},
"effective_agent": "web-server-01"
}
The hash is computed as: SHA256(JSON.dumps(components, sort_keys=True))
decision_id: "a3f5b2c8d9e1f4a7b6c3d8e2f5a9b4c7d1e8f3a6b9c2d5e8f1a4b7c0d3e6f9a2"
This ID appears in:
Mitigations obey:
allow_mitigation = false by defaultThe system implements comprehensive error handling and structured logging throughout the execution pipeline.
All logs use structured JSON format written to /var/ossec/logs/active-responses.log:
2026-02-06T10:15:30Z [INFO] RADAR Active Response started
2026-02-06T10:15:30Z [INFO] Config loaded {"path":"/var/ossec/active-response/ar.yaml"}
2026-02-06T10:15:30Z [INFO] Events {"events":[...],"iocs":{...}}
2026-02-06T10:15:31Z [INFO] SATRAP mock enrichment {"malicious":true}
2026-02-06T10:15:31Z [INFO] Risk computed {"risk_score":0.4795,"tier":2,"threshold":0.5}
2026-02-06T10:15:31Z [INFO] Actions planned {"tier":2,"allow_mitigation":true,"mitigations":["firewall_drop"]}
2026-02-06T10:15:32Z [INFO] Wazuh Active Response dispatched {"agent_id":"001","command":"firewall_drop","args":["203.0.113.42"]}
2026-02-06T10:15:32Z [INFO] RADAR Active Response completed {"decision_id":"a3f5b2c8...","exec_results":{...}}
| Level | Usage | Examples |
|---|---|---|
INFO |
Normal operation milestones | Config loaded, risk computed, actions executed |
WARNING |
Recoverable issues | No scenario matched, API lookup fallback, missing optional config |
ERROR |
Failed operations (non-critical) | Mitigation skipped, email send failed, Flowintel API error |
CRITICAL |
Unhandled exceptions | System-level failures that prevent execution |
Scenario: Invalid or missing alert JSON from stdin
Scenario: Missing or invalid ar.yaml
OpenSearch Connection:
Wazuh API:
SATRAP/CTI:
Email/SMTP:
Flowintel:
Scenario: Missing IOCs for command arguments
Scenario: Wazuh Active Response dispatch fails
Unhandled exceptions are caught at the top level (main() try-except):
except Exception as e:
Logger(...).log(
"CRITICAL",
"Unhandled exception",
error=str(e),
trace=traceback.format_exc()
)
sys.exit(2)
This ensures all failures are logged even if the logging system itself has issues.
The script loads environment variables from active_responses.env file located at:
/var/ossec/active-response/bin/active_responses.env (production){script_directory}/active_responses.env (development)The EnvLoader class parses the file and sets default values if not specified.
# OpenSearch Configuration
OS_URL=https://opensearch.example.com:9200
OS_USER=admin
OS_PASS=SecurePassword123
OS_VERIFY_SSL=false
OS_INDEXES=wazuh-alerts-*,wazuh-archives-*
# Email/SMTP Configuration
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_USER=radar@example.com
SMTP_PASS=EmailPassword123
EMAIL_FROM=radar@example.com
EMAIL_TO=soc-team@example.com
SMTP_STARTTLS=yes
# Wazuh API Configuration
WAZUH_API_URL=https://wazuh-manager.example.com:55000
WAZUH_AUTH_USER=admin
WAZUH_AUTH_PASS=WazuhPassword123
WAZUH_VERIFY_SSL=false
WAZUH_TIMEOUT_SEC=30
# Flowintel Configuration
FLOWINTEL_BASE_URL=https://flowintel.example.com
FLOWINTEL_API_KEY=your-api-key-here
FLOWINTEL_VERIFY_SSL=true
PYFLOWINTEL_PATH=/var/ossec/active-response/pyflowintel
# Script Configuration
AR_LOG_FILE=/var/ossec/logs/active-responses.log
AR_RISK_CONFIG=/var/ossec/active-response/ar.yaml
| Variable | Default | Description |
|---|---|---|
OS_URL |
https://localhost:9200 |
OpenSearch cluster URL |
OS_USER |
admin |
OpenSearch authentication username |
OS_PASS |
"" |
OpenSearch authentication password |
OS_VERIFY_SSL |
false |
Verify SSL certificates for OpenSearch |
OS_INDEXES |
wazuh-alerts-*,wazuh-archives-* |
Comma-separated index patterns to query |
| Variable | Default | Description |
|---|---|---|
SMTP_HOST |
smtp.office365.com |
SMTP server hostname |
SMTP_PORT |
587 |
SMTP server port |
SMTP_USER |
"" |
SMTP authentication username |
SMTP_PASS |
"" |
SMTP authentication password |
EMAIL_FROM |
{SMTP_USER} |
Email sender address |
EMAIL_TO |
"" |
Email recipient address (required) |
SMTP_STARTTLS |
yes |
Use STARTTLS for SMTP connection |
Note: If SMTP_USER or EMAIL_TO are empty, email notifications are skipped with a warning log.
| Variable | Default | Description |
|---|---|---|
WAZUH_API_URL |
"" |
Wazuh manager API base URL (e.g., https://host:55000) |
WAZUH_AUTH_USER |
"" |
Wazuh API authentication username |
WAZUH_AUTH_PASS |
"" |
Wazuh API authentication password |
WAZUH_VERIFY_SSL |
false |
Verify SSL certificates for Wazuh API |
WAZUH_TIMEOUT_SEC |
30 |
API request timeout in seconds |
Note: Required for mitigation execution. Script initializes client but mitigations will fail if credentials are invalid.
| Variable | Default | Description |
|---|---|---|
FLOWINTEL_BASE_URL |
"" |
Flowintel instance base URL |
FLOWINTEL_API_KEY |
"" |
Flowintel API key for authentication |
FLOWINTEL_VERIFY_SSL |
true |
Verify SSL certificates for Flowintel API |
PYFLOWINTEL_PATH |
/var/ossec/active-response/pyflowintel |
Path to pyflowintel library |
Note: Required for Tier 2/3 case creation. If URL or API key are empty, case creation is skipped with error log.
| Variable | Default | Description |
|---|---|---|
AR_LOG_FILE |
/var/ossec/logs/active-responses.log |
Path to structured log file |
AR_RISK_CONFIG |
/var/ossec/active-response/ar.yaml |
Path to scenario configuration YAML |
The EnvLoader supports:
KEY=value # comment (comments are stripped)KEY="value with spaces"ar.yamlThe configuration file is located at /var/ossec/active-response/ar.yaml (or path specified by AR_RISK_CONFIG environment variable) and defines scenario mappings, risk parameters, and response actions.
# Global tier boundaries (optional)
tiers:
tier1_max: 0.33 # Low risk upper bound
tier2_max: 0.66 # Medium risk upper bound
# Scenario definitions
scenarios:
geoip_detection:
# Scenario identification
rules: [210012, 210013, 210014]
detection: signature
# Risk calculation weights (must sum to 1.0)
w_ad: 0.0
w_sig: 0.6
w_cti: 0.4
# Signature risk parameters
signature_likelihood: 0.8 # Can be scalar or list (see below)
signature_impact: 0.75
# Time windows for context collection
delta_ad_minutes: 10
delta_signature_minutes: 1
# Risk threshold (optional, for filtering)
risk_threshold: 0.5
# Mitigation configuration
allow_mitigation: false # CRITICAL: Enable only after testing
mitigations:
- firewall_drop
suspicious_login:
rules: [210020, 210021]
detection: ad
w_ad: 0.3
w_sig: 0.4
w_cti: 0.3
signature_likelihood:
- rule_id: [210020]
weight: 0.6
- rule_id: [210021]
weight: 0.8
signature_impact: 0.9
allow_mitigation: false
mitigations:
- lock_user_linux
- firewall_drop
| Parameter | Type | Default | Description |
|---|---|---|---|
tiers.tier1_max |
float | 0.33 |
Upper bound of Tier 1 (low risk) |
tiers.tier2_max |
float | 0.66 |
Upper bound of Tier 2 (medium risk) |
Identification:
rules: List of Wazuh rule IDs that trigger this scenariodetection: "signature" or "ad" (affects time window and AD score extraction)Risk Weights (must sum to 1.0):
w_ad: Weight for anomaly detection component (0.0-1.0)w_sig: Weight for signature-based component (0.0-1.0)w_cti: Weight for CTI component (0.0-1.0)Signature Risk:
signature_likelihood: Scalar (float) or list of rule-specific weightssignature_impact: Impact value (0.0-1.0)Time Windows:
delta_ad_minutes: Lookback window for AD-based detection (default: 10)delta_signature_minutes: Lookback window for signature-based detection (default: 1)Mitigation:
allow_mitigation: Boolean flag (default: false) - MUST be explicitly enabledmitigations: List of mitigation commands to execute (Tier 2+)risk_threshold: Optional minimum risk score for responseScalar mode (single value for all rules):
signature_likelihood: 0.8
List mode (rule-specific weights):
signature_likelihood:
- rule_id: [210012, 210013]
weight: 0.5
- rule_id: [210020]
weight: 0.9
In list mode, if a rule ID doesn’t match any entry, likelihood defaults to 0.0.
| Command | Required IOC | Argument | Description |
|---|---|---|---|
firewall_drop |
ip |
First IP address | Block IP at firewall level |
lock_user_linux |
user |
First username | Lock user account on Linux systems |
terminate_service |
service |
First service name | Stop specified service |
Note: Custom active response scripts must be registered in Wazuh configuration (/var/ossec/etc/ossec.conf) and deployed to agents.
When deploying to production:
allow_mitigation: falseactive_responses.env with all required credentialstier1_max and tier2_max to match organizational risk tolerancew_ad, w_sig, w_cti based on detection source reliability/var/ossec/logs/active-responses.log for errors and false positivesFLOWINTEL_* environment variables for Tier 2+ integration