feat(harfanglab): map the fields the alert and threat payloads actually carry (v1.4.0)
Replayed a real get_alerts and a real get_threats response through the bundled mappers to see what came out. get_alerts: a sigma alert matching a Windows event log carries the whole record under `eventlog`, and none of it was mapped. On a Defender 1116 detection that left the malware, the file, the acting user and the detecting product invisible — the incident indexed a hostname and nothing else. The event data uses the provider's own field names, spaces included, so the new entries quote them with JSONata backticks, and Defender's "file:_" path prefix is stripped. Also added the agent IP (placed before details_connection.SourceIp so a network alert's own source still wins), the DNS domain, device type, agent version and groups, the tenant, the alert subtype and threat type, the string severity and the event time. get_threats: `level` had no entry at all, so a threat's severity never reached the OCSF document even though field_mappings derived the incident's 1-5 from it. Added it, the event time, and the top agent's reachability — whether the endpoint is online decides what response is possible, and it was excluded without a reason. Both trailers now record why each remaining field is left out, and the event log's own SYSTEM writer and FQDN spelling are called out: indexing them would create a junk user entity and a second spelling of one host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ ocsf:
|
||||
- { source_path: "id", ocsf_field: "finding_info.uid" }
|
||||
- { source_path: "slug", ocsf_field: "finding_info.title" }
|
||||
- { source_path: "creation_date", ocsf_field: "finding_info.created_time" }
|
||||
- { source_path: "creation_date", ocsf_field: "time" }
|
||||
- { source_path: "first_seen", ocsf_field: "finding_info.first_seen_time" }
|
||||
- { source_path: "last_seen", ocsf_field: "finding_info.last_seen_time" }
|
||||
# x ? x guards: these are null on open/mono-tenant threats — null is falsy in
|
||||
@@ -18,6 +19,10 @@ ocsf:
|
||||
# of writing a literal null into the OCSF document.
|
||||
- { source_path: "closed_date ? closed_date", ocsf_field: "end_time" }
|
||||
- { source_path: "status", ocsf_field: "status" }
|
||||
# field_mappings.severity turns this into the incident's 1-5 number; the string
|
||||
# itself has no other home, so without this entry the threat's own level never
|
||||
# reaches the OCSF document at all.
|
||||
- { source_path: "level", ocsf_field: "severity" } # low | medium | high | critical
|
||||
- { source_path: "linked_threat ? linked_threat", ocsf_field: "finding_info.uid_alt" }
|
||||
- { source_path: "tenant ? tenant", ocsf_field: "metadata.tenant_uid" }
|
||||
# ── Aggregate counts ──────────────────────────────────────────────
|
||||
@@ -35,6 +40,10 @@ ocsf:
|
||||
- { source_path: "top_agents[0].agent_osproducttype", ocsf_field: "src_endpoint.os.name" }
|
||||
- { source_path: "top_agents[0].agent_ostype", ocsf_field: "src_endpoint.os.type" }
|
||||
- { source_path: "top_agents[0].agent_osversion", ocsf_field: "device.os.version" }
|
||||
# Whether the agent is still reachable decides what response is even possible
|
||||
# (isolate, collect, kill) — the first thing asked of a threat, so it travels
|
||||
# with the endpoint rather than being left to the console.
|
||||
- { source_path: "top_agents[0].agent_status", ocsf_field: "unmapped.agent_status" }
|
||||
# ── Top impacted user ─────────────────────────────────────────────
|
||||
- { source_path: "top_impacted_users[0].user_name", ocsf_field: "user.name" }
|
||||
- { source_path: "top_impacted_users[0].user_sid", ocsf_field: "user.uid" }
|
||||
@@ -43,8 +52,13 @@ ocsf:
|
||||
- { source_path: "top_rules[0].rule_id", ocsf_field: "rule.uid" }
|
||||
- { source_path: "top_rules[0].rule_type", ocsf_field: "finding_info.analytic.type" }
|
||||
- { source_path: "top_rules[0].rule_msg", ocsf_field: "finding_info.desc" }
|
||||
# Deliberately unmapped: old_id/origin_stack (legacy/console internals), agent_status,
|
||||
# Deliberately unmapped: old_id/origin_stack (legacy/console internals),
|
||||
# top_rules[0].id (numeric console id — rule_id is the stable uid, mapped above),
|
||||
# per-item security_event_count, rule_os/rule_level/rule creation dates (no OCSF home),
|
||||
# top_rules[0].description (duplicate of rule_msg), and entries [1..] of the top_* arrays
|
||||
# (OCSF detection_finding models a single primary endpoint/user/rule; counts convey scale).
|
||||
# per-item security_event_count (equals the total while agent_count is 1, and the total
|
||||
# is already mapped), top_rules[0].rule_level (the same value as the threat's own level,
|
||||
# mapped to severity above), rule_os (the same value as the agent's ostype), the rule's
|
||||
# creation_date/last_update (rule bookkeeping, not incident evidence),
|
||||
# mitre_tactics[*].security_event_percentage (the tactic names are what pivot; the
|
||||
# per-tactic weights have no OCSF home), top_rules[0].description (duplicate of rule_msg),
|
||||
# and entries [1..] of the top_* arrays (OCSF detection_finding models a single primary
|
||||
# endpoint/user/rule; counts convey scale).
|
||||
|
||||
Reference in New Issue
Block a user