Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol audit analyzer.ex
Raw

lib/raxol/audit/analyzer.ex

defmodule Raxol.Audit.Analyzer do
@moduledoc """
Analyzes audit events in real-time to detect security threats, anomalies,
and compliance violations.
This module implements various detection algorithms including:
- Brute force attack detection
- Privilege escalation attempts
- Data exfiltration patterns
- Unusual access patterns
- Compliance violations
"""
use Raxol.Core.Behaviours.BaseManager
alias Raxol.Core.Runtime.Log
alias Raxol.Core.Utils.TimerManager
defstruct [
:config,
:detection_rules,
:alert_thresholds,
:user_profiles,
:anomaly_detector,
:threat_indicators,
:compliance_rules,
:recent_events,
:metrics
]
@type detection_rule :: %{
name: String.t(),
type: :threshold | :pattern | :anomaly | :correlation,
condition: function(),
severity: :low | :medium | :high | :critical,
action: :alert | :block | :investigate
}
## Client API
# BaseManager provides start_link/1
# Usage: Raxol.Audit.Analyzer.start_link(name: __MODULE__, config: config)
@doc """
Analyzes an audit event for threats and anomalies.
"""
def analyze_event(analyzer \\ __MODULE__, event) do
GenServer.call(analyzer, {:analyze_event, event})
end
@doc """
Analyzes a batch of events for correlations.
"""
def analyze_batch(analyzer \\ __MODULE__, events) do
GenServer.call(analyzer, {:analyze_batch, events})
end
@doc """
Gets current threat level assessment.
"""
def get_threat_assessment(analyzer \\ __MODULE__) do
GenServer.call(analyzer, :get_threat_assessment)
end
@doc """
Gets compliance status.
"""
def get_compliance_status(analyzer \\ __MODULE__) do
GenServer.call(analyzer, :get_compliance_status)
end
## BaseManager Implementation
@impl Raxol.Core.Behaviours.BaseManager
def init_manager(opts) do
config = Keyword.get(opts, :config, %{})
state = %__MODULE__{
config: config,
detection_rules: init_detection_rules(),
alert_thresholds: init_alert_thresholds(),
user_profiles: %{},
anomaly_detector: init_anomaly_detector(),
threat_indicators: init_threat_indicators(),
compliance_rules: init_compliance_rules(config),
recent_events: :queue.new(),
metrics: init_metrics()
}
# Schedule periodic analysis tasks using TimerManager
intervals = TimerManager.intervals()
{:ok, _} = TimerManager.start_interval(:analyze_patterns, intervals.minute)
{:ok, _} =
TimerManager.start_interval(:update_profiles, intervals.five_minutes)
Log.info("Audit analyzer initialized")
{:ok, state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call({:analyze_event, event}, _from, state) do
{analysis_result, new_state} = perform_analysis(event, state)
{:reply, analysis_result, new_state}
end
def handle_manager_call({:analyze_batch, events}, _from, state) do
{analysis_results, new_state} = perform_batch_analysis(events, state)
{:reply, analysis_results, new_state}
end
def handle_manager_call(:get_threat_assessment, _from, state) do
assessment = calculate_threat_assessment(state)
{:reply, {:ok, assessment}, state}
end
def handle_manager_call(:get_compliance_status, _from, state) do
status = check_compliance_status(state)
{:reply, {:ok, status}, state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_info(:analyze_patterns, state) do
new_state = analyze_recent_patterns(state)
{:noreply, new_state}
end
def handle_manager_info(:update_profiles, state) do
new_state = update_user_profiles(state)
{:noreply, new_state}
end
## Private Functions
defp perform_analysis(event, state) do
# Add event to recent events queue
new_recent = :queue.in(event, state.recent_events)
new_recent = trim_queue_if_needed(new_recent, 1000)
# Run detection rules
detections = run_detection_rules(event, state.detection_rules, state)
# Check for anomalies
anomalies = detect_anomalies(event, state)
# Check compliance
compliance_violations = check_compliance(event, state.compliance_rules)
# Update user profile
new_profiles = update_user_profile(event, state.user_profiles)
# Update metrics
new_metrics = update_metrics(event, detections, state.metrics)
# Build analysis result
analysis_result = %{
event_id: event.event_id,
timestamp: event.timestamp,
detections: detections,
anomalies: anomalies,
compliance_violations: compliance_violations,
risk_score: calculate_risk_score(detections, anomalies),
recommended_actions: determine_actions(detections, anomalies)
}
new_state = %{
state
| recent_events: new_recent,
user_profiles: new_profiles,
metrics: new_metrics
}
# Trigger alerts if needed
_ = maybe_send_alert(analysis_result, state)
{analysis_result, new_state}
end
defp perform_batch_analysis(events, state) do
# Detect correlated patterns
correlations = detect_correlations(events, state)
# Check for attack patterns
attack_patterns = detect_attack_patterns(events, state)
# Analyze data flow
data_flow_anomalies = analyze_data_flow(events, state)
analysis_results = %{
correlations: correlations,
attack_patterns: attack_patterns,
data_flow_anomalies: data_flow_anomalies,
batch_risk_score:
calculate_batch_risk_score(correlations, attack_patterns)
}
{analysis_results, state}
end
defp run_detection_rules(event, rules, state) do
rules
|> Enum.filter(fn rule -> rule.condition.(event, state) end)
|> Enum.map(fn rule ->
%{
rule_name: rule.name,
type: rule.type,
severity: rule.severity,
action: rule.action,
details: extract_rule_details(rule, event)
}
end)
end
defp detect_anomalies(event, state) do
anomalies = []
# Check for unusual time of activity
anomalies = add_time_anomaly_if_needed(anomalies, event, state)
# Check for unusual location
anomalies = add_location_anomaly_if_needed(anomalies, event, state)
# Check for unusual behavior pattern
anomalies = add_behavior_anomaly_if_needed(anomalies, event, state)
anomalies
end
defp detect_correlations(events, _state) do
correlations = []
# Check for rapid succession of failed authentications
failed_auth_count =
Enum.count(events, fn e ->
Map.get(e, :event_type) == :authentication and
Map.get(e, :outcome) == :failure
end)
correlations_with_auth =
add_brute_force_correlation(correlations, failed_auth_count)
# Check for data exfiltration pattern
data_access_events =
Enum.filter(events, fn e ->
Map.get(e, :operation) in [:read, :export]
end)
check_and_add_exfiltration_pattern(
correlations_with_auth,
data_access_events
)
end
defp detect_attack_patterns(events, _state) do
patterns = []
# Check for privilege escalation attempts
priv_events =
Enum.filter(events, fn e ->
Map.get(e, :action) == :privilege_escalation or
String.contains?(Map.get(e, :command, ""), ["sudo", "su", "chmod"])
end)
patterns_with_priv = add_privilege_escalation_pattern(patterns, priv_events)
# Check for reconnaissance patterns
recon_commands = [
"whoami",
"id",
"uname",
"ps",
"netstat",
"ifconfig",
"ls -la"
]
recon_events =
Enum.filter(events, fn e ->
command = Map.get(e, :command, "")
Enum.any?(recon_commands, &String.contains?(command, &1))
end)
add_reconnaissance_pattern(patterns_with_priv, recon_events)
end
defp analyze_data_flow(events, _state) do
# Group events by user
by_user = Enum.group_by(events, &Map.get(&1, :user_id))
Enum.flat_map(by_user, fn {user_id, user_events} ->
# Check for unusual data access patterns
resources_accessed =
user_events
|> Enum.map(&Map.get(&1, :resource_id))
|> Enum.filter(&(&1 != nil))
|> Enum.uniq()
|> length()
check_excessive_resource_access(user_id, resources_accessed)
end)
end
defp check_compliance(event, compliance_rules) do
compliance_rules
|> Enum.filter(fn rule -> rule.check.(event) end)
|> Enum.map(fn rule ->
%{
framework: rule.framework,
requirement: rule.requirement,
violation: rule.violation_type,
severity: rule.severity,
remediation: rule.remediation
}
end)
end
defp unusual_time?(event, state) do
hour =
event.timestamp
|> DateTime.from_unix!(:millisecond)
|> Map.get(:hour)
user_id = Map.get(event, :user_id)
user_profile = Map.get(state.user_profiles, user_id, %{})
usual_hours = Map.get(user_profile, :usual_hours, 8..18)
hour not in usual_hours
end
defp unusual_location?(%{ip_address: nil}, _state), do: false
defp unusual_location?(event, state) do
user_id = Map.get(event, :user_id)
user_profile = Map.get(state.user_profiles, user_id, %{})
known_ips = Map.get(user_profile, :known_ips, MapSet.new())
ip = Map.get(event, :ip_address)
not MapSet.member?(known_ips, ip)
end
defp unusual_behavior?(event, state) do
# Simplified behavior analysis
user_id = Map.get(event, :user_id)
user_profile = Map.get(state.user_profiles, user_id, %{})
baseline = Map.get(user_profile, :behavior_baseline, %{})
# Check if event type is unusual for this user
event_type = Map.get(event, :event_type)
usual_events = Map.get(baseline, :common_events, [])
event_type not in usual_events and length(usual_events) > 10
end
defp calculate_risk_score(detections, anomalies) do
detection_score =
Enum.reduce(detections, 0, fn detection, acc ->
acc + severity_score(detection.severity)
end)
anomaly_score =
Enum.reduce(anomalies, 0, fn anomaly, acc ->
acc + severity_score(anomaly.severity)
end)
min(100, detection_score + anomaly_score)
end
defp calculate_batch_risk_score(correlations, patterns) do
correlation_score =
Enum.reduce(correlations, 0, fn corr, acc ->
acc + severity_score(corr.severity)
end)
pattern_score =
Enum.reduce(patterns, 0, fn pattern, acc ->
acc + severity_score(pattern.severity)
end)
min(100, correlation_score * 2 + pattern_score * 3)
end
defp severity_score(:low), do: 10
defp severity_score(:medium), do: 25
defp severity_score(:high), do: 50
defp severity_score(:critical), do: 100
defp determine_actions(detections, anomalies) do
actions = []
critical_detection = Enum.any?(detections, &(&1.severity == :critical))
high_detection = Enum.any?(detections, &(&1.severity == :high))
actions = add_critical_actions(actions, critical_detection)
actions = add_high_priority_actions(actions, high_detection)
add_anomaly_review_if_needed(actions, anomalies)
end
defp should_alert?(analysis_result) do
analysis_result.risk_score > 50 or
Enum.any?(
analysis_result.detections,
&(&1.severity in [:high, :critical])
) or
length(analysis_result.compliance_violations) > 0
end
defp send_security_alert(analysis_result, _state) do
Task.start(fn ->
Log.warning("Security Alert: #{inspect(analysis_result)}")
# Here you would integrate with alerting systems (PagerDuty, Slack, etc.)
end)
end
defp update_user_profile(%{user_id: nil}, profiles), do: profiles
defp update_user_profile(event, profiles) do
user_id = Map.get(event, :user_id)
profile =
Map.get(profiles, user_id, %{
usual_hours: MapSet.new(),
known_ips: MapSet.new(),
common_events: [],
behavior_baseline: %{}
})
# Update profile with new data
hour =
event.timestamp
|> DateTime.from_unix!(:millisecond)
|> Map.get(:hour)
updated_profile =
profile
|> Map.update!(:usual_hours, &MapSet.put(&1, hour))
|> Map.update!(:known_ips, &update_known_ips(&1, event))
|> Map.update!(:common_events, &update_common_events(&1, event))
Map.put(profiles, user_id, updated_profile)
end
defp analyze_recent_patterns(state) do
recent_list = :queue.to_list(state.recent_events)
# Analyze patterns in recent events
patterns = detect_attack_patterns(recent_list, state)
log_patterns_if_found(patterns)
state
end
defp update_user_profiles(state) do
# Periodically refine user profiles based on accumulated data
state
end
defp calculate_threat_assessment(state) do
recent_list = :queue.to_list(state.recent_events)
critical_events =
Enum.count(recent_list, fn e ->
Map.get(e, :severity) == :critical
end)
high_events =
Enum.count(recent_list, fn e ->
Map.get(e, :severity) == :high
end)
threat_level = determine_threat_level(critical_events, high_events)
%{
threat_level: threat_level,
critical_events: critical_events,
high_events: high_events,
total_events: length(recent_list),
assessment_time: System.system_time(:millisecond)
}
end
defp check_compliance_status(_state) do
# Check current compliance status
%{
frameworks: [:soc2, :hipaa, :gdpr, :pci_dss],
status: :compliant,
last_check: System.system_time(:millisecond),
findings: []
}
end
defp init_detection_rules do
[
%{
name: "brute_force_detection",
type: :threshold,
condition: fn event, _state ->
Map.get(event, :event_type) == :authentication and
Map.get(event, :outcome) == :failure
end,
severity: :high,
action: :alert
},
%{
name: "suspicious_command_execution",
type: :pattern,
condition: fn event, _state ->
command =
case event do
%{command: cmd} when is_binary(cmd) -> cmd
_ -> Map.get(event, :command, "")
end
suspicious = ["curl", "wget", "rm -rf", ":()", "| sh", "| bash"]
Enum.any?(suspicious, &String.contains?(String.downcase(command), &1))
end,
severity: :critical,
action: :block
},
%{
name: "unauthorized_access_attempt",
type: :pattern,
condition: fn event, _state ->
Map.get(event, :event_type) == :authorization and
Map.get(event, :outcome) == :denied
end,
severity: :medium,
action: :alert
},
%{
name: "security_event_detected",
type: :pattern,
condition: fn event, _state ->
Map.get(event, :event_type) == :security or
Map.get(event, :action) == :intrusion_detected
end,
severity: :high,
action: :investigate
}
]
end
defp init_alert_thresholds do
%{
failed_auth_threshold: 5,
data_access_threshold: 1000,
configuration_changes_threshold: 10,
time_window_minutes: 5
}
end
defp init_anomaly_detector do
%{
baseline_window_days: 30,
# Standard deviations
deviation_threshold: 3.0,
min_data_points: 100
}
end
defp init_threat_indicators do
%{
known_bad_ips: MapSet.new(),
suspicious_user_agents: [],
malicious_patterns: []
}
end
defp init_compliance_rules(_config) do
[
%{
framework: :gdpr,
requirement: "Data access logging",
check: fn event ->
Map.get(event, :event_type) == :data_access and
Map.get(event, :data_classification) == :personal and
Map.get(event, :legal_basis) == nil
end,
violation_type: :missing_legal_basis,
severity: :high,
remediation: "Ensure legal basis is documented for personal data access"
},
%{
framework: :hipaa,
requirement: "PHI access control",
check: fn event ->
Map.get(event, :data_classification) == :phi and
Map.get(event, :mfa_used) == false
end,
violation_type: :missing_mfa,
severity: :critical,
remediation: "MFA required for PHI access"
}
]
end
defp init_metrics do
%{
events_analyzed: 0,
detections_triggered: 0,
anomalies_detected: 0,
compliance_violations: 0,
alerts_sent: 0,
start_time: System.system_time(:millisecond)
}
end
defp update_metrics(_event, detections, metrics) do
metrics
|> Map.update!(:events_analyzed, &(&1 + 1))
|> Map.update!(:detections_triggered, &(&1 + length(detections)))
end
defp extract_rule_details(_rule, event) do
%{
triggered_at: event.timestamp,
event_id: event.event_id,
user_id: Map.get(event, :user_id),
resource: Map.get(event, :resource_id)
}
end
# Helper functions for pattern matching refactoring
defp determine_threat_level(critical_events, _high_events)
when critical_events > 5,
do: :critical
defp determine_threat_level(critical_events, _high_events)
when critical_events > 0,
do: :high
defp determine_threat_level(_critical_events, high_events)
when high_events > 10,
do: :high
defp determine_threat_level(_critical_events, high_events)
when high_events > 5,
do: :medium
defp determine_threat_level(_critical_events, _high_events), do: :low
defp trim_queue_if_needed(queue, max_length) do
case :queue.len(queue) > max_length do
true ->
{_, trimmed} = :queue.out(queue)
trimmed
false ->
queue
end
end
defp maybe_send_alert(analysis_result, state) do
case should_alert?(analysis_result) do
true -> send_security_alert(analysis_result, state)
false -> :ok
end
end
defp add_time_anomaly_if_needed(anomalies, event, state) do
case unusual_time?(event, state) do
true ->
[
%{
type: :unusual_time,
severity: :low,
details: "Activity at unusual hour"
}
| anomalies
]
false ->
anomalies
end
end
defp add_location_anomaly_if_needed(anomalies, event, state) do
case unusual_location?(event, state) do
true ->
[
%{
type: :unusual_location,
severity: :medium,
details: "Access from new location"
}
| anomalies
]
false ->
anomalies
end
end
defp add_behavior_anomaly_if_needed(anomalies, event, state) do
case unusual_behavior?(event, state) do
true ->
[
%{
type: :unusual_behavior,
severity: :high,
details: "Behavior deviates from baseline"
}
| anomalies
]
false ->
anomalies
end
end
defp add_brute_force_correlation(correlations, failed_auth_count)
when failed_auth_count > 5 do
[
%{
type: :brute_force_attempt,
severity: :high,
event_count: failed_auth_count,
details: "Multiple failed authentication attempts detected"
}
| correlations
]
end
defp add_brute_force_correlation(correlations, _failed_auth_count),
do: correlations
defp check_and_add_exfiltration_pattern(correlations, data_access_events)
when length(data_access_events) > 10 do
total_records =
Enum.sum(Enum.map(data_access_events, &Map.get(&1, :records_count, 0)))
add_exfiltration_if_high_volume(correlations, total_records)
end
defp check_and_add_exfiltration_pattern(correlations, _data_access_events),
do: correlations
defp add_exfiltration_if_high_volume(correlations, total_records)
when total_records > 1000 do
[
%{
type: :potential_data_exfiltration,
severity: :critical,
records_accessed: total_records,
details: "Large volume of data accessed in short time"
}
| correlations
]
end
defp add_exfiltration_if_high_volume(correlations, _total_records),
do: correlations
defp add_critical_actions(actions, true) do
[
:immediate_investigation,
:notify_security_team,
:consider_blocking | actions
]
end
defp add_critical_actions(actions, false), do: actions
defp add_high_priority_actions(actions, true) do
[:investigate, :increase_monitoring | actions]
end
defp add_high_priority_actions(actions, false), do: actions
defp add_anomaly_review_if_needed(actions, anomalies)
when length(anomalies) > 3 do
[:review_user_activity | actions]
end
defp add_anomaly_review_if_needed(actions, _anomalies), do: actions
defp update_known_ips(ips, %{ip_address: nil}), do: ips
defp update_known_ips(ips, %{ip_address: ip}), do: MapSet.put(ips, ip)
defp update_known_ips(ips, _event), do: ips
defp update_common_events(events, event) do
event_type = Map.get(event, :event_type)
case event_type in events do
true -> events
# Keep last 20 event types
false -> [event_type | Enum.take(events, 19)]
end
end
defp log_patterns_if_found([]), do: :ok
defp log_patterns_if_found(patterns) do
Log.warning("Patterns detected in recent events: #{inspect(patterns)}")
end
defp add_privilege_escalation_pattern(patterns, priv_events)
when length(priv_events) > 3 do
[
%{
type: :privilege_escalation_attempt,
severity: :critical,
event_count: length(priv_events),
details: "Multiple privilege escalation attempts detected"
}
| patterns
]
end
defp add_privilege_escalation_pattern(patterns, _priv_events), do: patterns
defp add_reconnaissance_pattern(patterns, recon_events)
when length(recon_events) > 5 do
[
%{
type: :reconnaissance_activity,
severity: :medium,
event_count: length(recon_events),
details: "System reconnaissance activity detected"
}
| patterns
]
end
defp add_reconnaissance_pattern(patterns, _recon_events), do: patterns
defp check_excessive_resource_access(user_id, resources_accessed)
when resources_accessed > 50 do
[
%{
type: :excessive_resource_access,
user_id: user_id,
severity: :medium,
resources_count: resources_accessed,
details: "User accessed unusually high number of resources"
}
]
end
defp check_excessive_resource_access(_user_id, _resources_accessed), do: []
end