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))" description: "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 ─────────────────────────────────── - { 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), # `image_name` and eventlog.threat_process_name (both prefixed with the threat name — # the clean path comes from the event data), `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.