Files
riposte-marketplace/integrations/harfanglab/mappers/get_alerts.yaml
T
f3nris 5cf870309f feat(harfanglab): describe an incident with the rule's message (1.7.1)
The get_security_events and get_alerts mappers set an incident's
description to alert_type alone — "sigma", "yara" — which says nothing
an analyst can act on. It now takes msg, the rule's own account of what
it detects and what to check, and falls back to the alert type only on
alerts that carry no message. Folded into the unreleased 1.7.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXFMuDDLohs4qnL12mBJoP
2026-10-07 19:11:35 +00:00

139 lines
12 KiB
YAML

name: "HarfangLab Alerts → OCSF"
description: "Maps a HarfangLab EDR alert (/api/data/alert/alert/Alert/, results_path = results) to OCSF. The matched process is the actor (actor.*). Detail sub-objects and the eventlog payload are only present for their event kind; absent paths are skipped at ingestion."
field_mappings:
title: "rule_name"
severity: "level = 'critical' ? 5 : (level = 'high' ? 4 : (level = 'medium' ? 3 : 2))"
# msg is the rule's own explanation of what it detects; the alert type
# (sigma, yara, ioc…) stands in only on the alerts that carry none.
description: "msg ? msg : alert_type"
# results_path = results; source_path is JSONata over ONE alert object.
# Paths absent from a given alert are skipped at ingestion, so extra entries are safe.
ocsf:
# ── Endpoint ──────────────────────────────────────────────────────
- { source_path: "agent.hostname", ocsf_field: "src_endpoint.hostname" }
- { source_path: "agent.domainname", ocsf_field: "src_endpoint.domain" }
# Before details_connection.SourceIp below, so a network alert's own source
# address wins over the agent's; on every other alert this is the only IP.
- { source_path: "agent.ipaddress", ocsf_field: "src_endpoint.ip" }
- { source_path: "agent.osproducttype", ocsf_field: "src_endpoint.os.name" }
- { source_path: "agent.ostype", ocsf_field: "src_endpoint.os.type" }
- { source_path: "agent.osversion", ocsf_field: "device.os.version" }
- { source_path: "agent.agentid", ocsf_field: "device.uid" }
- { source_path: "agent.ipaddress", ocsf_field: "device.ip" }
- { source_path: "agent.dnsdomainname", ocsf_field: "device.domain" }
- { source_path: "agent.producttype", ocsf_field: "device.type" } # workstation | server
# ── Finding ───────────────────────────────────────────────────────
- { source_path: "id", ocsf_field: "finding_info.uid" }
- { source_path: "alert_unique_id", ocsf_field: "metadata.uid" }
- { source_path: "rule_name", ocsf_field: "finding_info.title" }
- { source_path: "msg", ocsf_field: "finding_info.desc" }
- { source_path: "alert_time", ocsf_field: "finding_info.created_time" }
- { source_path: "alert_time", ocsf_field: "time" }
- { source_path: "detection_date", ocsf_field: "finding_info.first_seen_time" }
- { source_path: "last_update", ocsf_field: "finding_info.modified_time" }
- { source_path: "ingestion_date", ocsf_field: "metadata.logged_time" }
- { source_path: "alert_type", ocsf_field: "finding_info.analytic.type" }
- { source_path: "rule_id", ocsf_field: "finding_info.analytic.uid" }
- { source_path: "rule_content", ocsf_field: "finding_info.analytic.desc" }
- { source_path: "detection_origin", ocsf_field: "finding_info.analytic.category" }
- { source_path: "alert_subtype", ocsf_field: "unmapped.alert_subtype" } # eventlog | connection | dns | file | registry | url_request
- { source_path: "status", ocsf_field: "status" }
- { source_path: "maturity", ocsf_field: "status_detail" }
- { source_path: "threat_type", ocsf_field: "unmapped.threat_type" }
- { source_path: "level", ocsf_field: "severity" } # low | medium | high | critical
- { source_path: "tenant ? tenant", ocsf_field: "metadata.tenant_uid" }
- { source_path: "agent.version", ocsf_field: "unmapped.agent_version" }
# groups is an array of {id, name}; the [ ] keeps an array shape when a single
# group matches, since JSONata collapses a singleton sequence to a scalar.
- { source_path: "groups ? [groups.name]", ocsf_field: "unmapped.agent_groups" }
- { source_path: "score", ocsf_field: "risk_score" }
- { source_path: "confidence_int", ocsf_field: "confidence_score" }
- { source_path: "threat_key", ocsf_field: "finding_info.uid_alt" } # threat aggregation key
- { source_path: "mitre_cells", ocsf_field: "attacks" } # ["TAxxxx:Tyyyy", ...] best-effort
- { source_path: "tags", ocsf_field: "metadata.labels" }
- { source_path: "log_type", ocsf_field: "metadata.log_name" }
# ── Matched process — the actor ───────────────────────────────────
# image_name is the one process path present on EVERY alert subtype, and the
# only one on those carrying neither a `process` object nor an event log. When
# a threat matched, HarfangLab prefixes it with the threat name in brackets —
# "[PUADlManager:Win32/OfferCore] C:\Windows\explorer.exe" — so both halves are
# pulled out, each guarded on the bracket actually being there: without the
# $contains guard, $substringBefore/$substringAfter fall through on an
# unprefixed path and write the whole path into malware.name.
# These come first in the block so the more specific sources below (the matched
# process object, then the event log's own fields) overwrite them when present.
- { source_path: "$contains(image_name, '] ') ? $substringAfter(image_name, '] ') : image_name", ocsf_field: "actor.process.file.path" }
- { source_path: "$split($contains(image_name, '] ') ? $substringAfter(image_name, '] ') : image_name, '\\\\')[-1]", ocsf_field: "actor.process.name" }
- { source_path: "$contains(image_name, '[') ? $substringBefore($substringAfter(image_name, '['), ']')", ocsf_field: "malware.name" }
- { source_path: "process.process_name", ocsf_field: "actor.process.name" }
- { source_path: "process.image_name", ocsf_field: "actor.process.file.path" }
- { source_path: "process.commandline", ocsf_field: "actor.process.cmd_line" }
- { source_path: "process.username", ocsf_field: "actor.user.name" }
- { source_path: "process.integrity_level", ocsf_field: "actor.process.integrity" }
- { source_path: "process.pid", ocsf_field: "actor.process.pid" }
- { source_path: "process.hashes.sha256", ocsf_field: "actor.process.file.hashes.sha256" }
- { source_path: "process.hashes.sha1", ocsf_field: "actor.process.file.hashes.sha1" }
- { source_path: "process.hashes.md5", ocsf_field: "actor.process.file.hashes.md5" }
- { source_path: "process.parent_image", ocsf_field: "actor.process.parent_process.name" }
- { source_path: "process.parent_commandline", ocsf_field: "actor.process.parent_process.cmd_line" }
# ── Event details: network connection ─────────────────────────────
- { source_path: "details_connection.SourceIp", ocsf_field: "src_endpoint.ip" }
- { source_path: "details_connection.SourcePort", ocsf_field: "src_endpoint.port" }
- { source_path: "details_connection.DestinationIp", ocsf_field: "dst_endpoint.ip" }
- { source_path: "details_connection.DestinationPort", ocsf_field: "dst_endpoint.port" }
- { source_path: "details_connection.Protocol", ocsf_field: "connection_info.protocol_name" }
# ── Event details: DNS ────────────────────────────────────────────
- { source_path: "details_dns_resolution.requested_name", ocsf_field: "query.hostname" }
- { source_path: "details_dns_resolution.query_type", ocsf_field: "query.type" }
# ── Event details: file ───────────────────────────────────────────
- { source_path: "details_file.target_filename", ocsf_field: "file.path" }
# ── Event details: registry ───────────────────────────────────────
- { source_path: "details_registry.target_object", ocsf_field: "reg_key.path" }
- { source_path: "details_registry.registry_value_type", ocsf_field: "reg_value.type" }
- { source_path: "details_registry.data_string_added", ocsf_field: "reg_value.data" }
# ── Event details: URL request ────────────────────────────────────
- { source_path: "details_url_request.url", ocsf_field: "url.url_string" }
- { source_path: "details_url_request.host", ocsf_field: "url.hostname" }
- { source_path: "details_url_request.user_agent", ocsf_field: "http_request.user_agent" }
- { source_path: "details_url_request.verb", ocsf_field: "http_request.http_method" }
# ── Event details: Windows event log (alert_subtype = eventlog) ───
# A sigma rule matching a Windows event log carries the whole record under
# `eventlog`, and the provider's own field names — spaces included — under
# eventlog.event_data. Backticks quote those names in JSONata. The block below
# is Microsoft Defender (events 1116/1117), where the detection names the
# malware, the file it was found in, the process that touched it and the user
# it ran as. None of it exists on other subtypes, so it is skipped there.
- { source_path: "eventlog.source_name", ocsf_field: "metadata.log_provider" }
- { source_path: "eventlog.event_id", ocsf_field: "metadata.event_code" }
- { source_path: "eventlog.event_data.`Product Name`", ocsf_field: "metadata.product.name" }
- { source_path: "eventlog.event_data.`Product Version`", ocsf_field: "metadata.product.version" }
- { source_path: "eventlog.event_data.`Threat Name`", ocsf_field: "malware.name" }
- { source_path: "eventlog.event_data.`Threat ID`", ocsf_field: "malware.uid" }
- { source_path: "eventlog.event_data.`Severity Name`", ocsf_field: "malware.severity" }
- { source_path: "eventlog.event_data.`Product Name`", ocsf_field: "malware.provider" }
- { source_path: "eventlog.event_data.`Category Name` ? [eventlog.event_data.`Category Name`]", ocsf_field: "malware.classifications" }
# Defender prefixes the detected path with "file:_". $substringAfter returns the
# whole string when the prefix is absent, so stripping it is safe either way.
- { source_path: "$substringAfter(eventlog.event_data.`Path`, 'file:_')", ocsf_field: "file.path" }
- { source_path: "$substringAfter(eventlog.event_data.`Path`, 'file:_')", ocsf_field: "malware.path" }
- { source_path: "$split($substringAfter(eventlog.event_data.`Path`, 'file:_'), '\\\\')[-1]", ocsf_field: "file.name" }
- { source_path: "eventlog.event_data.`Process Name`", ocsf_field: "actor.process.file.path" }
- { source_path: "$split(eventlog.event_data.`Process Name`, '\\\\')[-1]", ocsf_field: "actor.process.name" }
- { source_path: "eventlog.event_data.`Detection User`", ocsf_field: "actor.user.name" }
- { source_path: "eventlog.event_data.`Action Name`", ocsf_field: "action" }
- { source_path: "eventlog.event_data.`Detection ID`", ocsf_field: "unmapped.defender.detection_uid" }
- { source_path: "eventlog.event_data.`Source Name`", ocsf_field: "unmapped.defender.detection_source" } # real-time protection, scheduled scan…
- { source_path: "eventlog.event_data.`Execution Name`", ocsf_field: "unmapped.defender.execution_state" }
- { source_path: "eventlog.event_data.`Security intelligence Version`", ocsf_field: "unmapped.defender.signature_version" }
# Alert fields deliberately left unmapped: `username` (actor.user.name is taken by the
# more specific process.username), remaining details_* sub-objects (no clean OCSF home),
# eventlog.threat_process_name (the same prefixed string as image_name, already split
# above), `eventlog.user` (the account that WROTE the
# record, usually SYSTEM — indexing it would create a junk user entity),
# `eventlog.computer_name` (the FQDN of the host already carried by agent.hostname; two
# spellings of one machine split correlation), `level_int` / `quarantine` / `execution`
# and Defender's Action ID / Severity ID / Category ID (console enums on scales OCSF does
# not share), `detection_timestamp` and Defender's Detection Time (duplicates of
# detection_date), `confidence` (the string form of confidence_int, already mapped), and
# Defender's Error/Status/Origin/Remediation/Unused fields.