815db080f5
The generic mapper now fills the OCSF fields a relayed message has a place for: metadata.logged_time, log_provider and log_name, the checks the delivery passed as metadata.labels (relay:hmac…), and raw_data. The sender's address goes to unmapped.relay.sender_ip rather than src_endpoint.ip: it is the vendor's cloud, and as an entity it would correlate every alert of that vendor. metadata.product.name no longer holds the source id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xa1QDbsPTYiVREv2Cvs3Zn
63 lines
4.3 KiB
YAML
63 lines
4.3 KiB
YAML
name: "Webhook Relay message (generic)"
|
|
description: |
|
|
Maps one relayed message — whatever the vendor sent — to an alert, without knowing
|
|
the vendor. It is the mapper a new source starts with: the alert exists, the raw
|
|
payload is readable in Riposte, and a vendor mapper is then written from it
|
|
(clone this one, or pick the vendor's own when the marketplace ships it).
|
|
|
|
A message is {id, source, received_at, remote_ip, content_type, headers, verified,
|
|
body | body_b64, json}. `json` is the body already decoded when it is JSON, so a
|
|
vendor field reads json.<field>.
|
|
|
|
The title tries the fields alert payloads most often name themselves with, and
|
|
falls back to the source. Severity is left to the alert rule's default: a generic
|
|
payload has no scale this mapper could trust.
|
|
|
|
The OCSF side carries what has a place in the schema: the relay's message id as the
|
|
finding uid, the receipt time (time and metadata.logged_time), the source as
|
|
metadata.log_name, the checks the delivery passed as metadata.labels (relay:hmac…),
|
|
the payload as raw_data. The sender's address goes to unmapped.relay.sender_ip and
|
|
NOT src_endpoint.ip: it is the vendor's cloud, and as an entity it would tie every
|
|
alert of that vendor together.
|
|
|
|
The custom fields repeat what layouts and automation conditions read most easily.
|
|
custom:webhook_verified is the trust level: the checks the delivery passed at the
|
|
relay (path_token, header_token, hmac, ip). An alert authenticated by a URL alone
|
|
is plausible, not proven — declare the field and keep destructive automation off
|
|
such alerts until a playbook has confirmed them against the vendor.
|
|
field_mappings:
|
|
title: "jsonata: $exists(json.title) ? $string(json.title) : $exists(json.name) ? $string(json.name) : $exists(json.alert.name) ? $string(json.alert.name) : $exists(json.rule.name) ? $string(json.rule.name) : $exists(json.detection_name) ? $string(json.detection_name) : ('Webhook · ' & source)"
|
|
source: "jsonata: 'Webhook relay · ' & source"
|
|
description: "jsonata: body ? $substring(body, 0, 8000) : ('Binary body, ' & $string($length(body_b64)) & ' base64 characters')"
|
|
"custom:webhook_source": "source"
|
|
"custom:webhook_verified": "jsonata: $join(verified, ',')"
|
|
"custom:webhook_message_id": "id"
|
|
"custom:webhook_received_at": "received_at"
|
|
"custom:webhook_sender_ip": "remote_ip"
|
|
# Key/value rows of the decoded payload's top level, for a table field.
|
|
"custom:webhook_payload": "jsonata: json ? $toTable(json) : body"
|
|
ocsf:
|
|
# ── Finding ───────────────────────────────────────────────────────
|
|
# The relay's message id: stable for one delivery. A vendor mapper replaces it with
|
|
# the vendor's own alert id.
|
|
- { source_path: "id", ocsf_field: "finding_info.uid" }
|
|
- { source_path: "id", ocsf_field: "metadata.uid" }
|
|
# When the relay received it — the best time a payload of unknown shape offers.
|
|
# A vendor mapper puts the detection's own time in `time` and keeps this one.
|
|
- { source_path: "received_at", ocsf_field: "time" }
|
|
- { source_path: "received_at", ocsf_field: "metadata.logged_time" }
|
|
# ── Where it came from ────────────────────────────────────────────
|
|
- { source_path: "'riposte-relay'", ocsf_field: "metadata.log_provider" }
|
|
- { source_path: "source", ocsf_field: "metadata.log_name" }
|
|
# The checks the delivery passed, as labels: relay:path_token, relay:hmac, relay:ip.
|
|
# Kept an array even with one check (the singleton trap).
|
|
- { source_path: "[verified.('relay:' & $)]", ocsf_field: "metadata.labels" }
|
|
# The payload exactly as sent: what an analyst and the case synthesis read when no
|
|
# vendor mapper exists yet.
|
|
- { source_path: "body", ocsf_field: "raw_data" }
|
|
# The sender's address is the VENDOR's (its cloud, its collector), never the
|
|
# attacker's or the victim's: as src_endpoint.ip it would become an entity, and
|
|
# every alert of that vendor would correlate on it. It stays out of the schema.
|
|
- { source_path: "remote_ip", ocsf_field: "unmapped.relay.sender_ip" }
|
|
- { source_path: "content_type", ocsf_field: "unmapped.relay.content_type" }
|