Skip to main content

Fleet SIEM Log Forwarding

SigmaShake Fleet's audit event stream can be forwarded to your existing SIEM or data pipeline in near real time. This is an Enterprise feature, configured per-org, and strictly opt-in — no audit data leaves the Fleet perimeter unless a destination is explicitly enabled.

Supported destinations

KindTransportAuthBatching
splunkHTTPS POST to Splunk HEC /services/collector/eventAuthorization: Splunk <hec_token>900 KB chunks, one HEC envelope per event
generic_webhookHTTPS POST (JSON body)HMAC-SHA256 signature header X-SigmaShake-Signature: t=<sec>,v1=<hex>1 MB chunks (configurable)
criblHTTPS POST to Cribl HTTP-in sourceAuthorization: Bearer <token>900 KB NDJSON chunks
confluent_restConfluent Cloud Kafka REST v3 streaming produceHTTP Basic (cluster-scoped Kafka API key)900 KB concatenated JSON records, org_id used as partition key

All adapters support both stream mode (per-event, low-latency) and archive mode (R2-backed content-addressed rollups with a Merkle root for tamper-evidence).

Delivery model

Fleet writes audit events to a dual-mode pipeline:

  • Stream path — each event is enqueued to a per-destination Cloudflare Queue and delivered to the SIEM within seconds. Each delivery runs on a 25-second budget with automatic exponential-backoff retry (2^attempt × 30 s, capped at 30 min, plus jitter). Non-retryable errors (4xx except 408/429) surface to the operator via last_error.
  • Archive path — every 5 minutes (or whenever a flush threshold is met), batched events are gzipped as NDJSON, written to a content-addressed R2 object (audit/residency=.../{sha256}.ndjson.gz), and fanned out to each enabled archive destination. Each object carries a Merkle root so downstream consumers can detect tampering.

Deliveries are idempotent at the batch level: a UNIQUE(destination_id, r2_key) constraint on the delivery log ensures an archive batch is never double-delivered even if a worker is retried after acknowledgement failure.

Configuring a destination

Destinations are managed via the Fleet REST API under /api/siem/destinations (enterprise license + siem:admin RBAC required). A destination row has:

  • kind — one of the adapter kinds above
  • modestream, archive, or both
  • config_json — non-secret adapter config (endpoint, cluster_id, topic, index, …)
  • credentials_ciphertext + credentials_iv — AES-GCM-encrypted credential blob (plaintext is never stored; see Credential handling)
  • redact_fields_json — optional dotted-path list (e.g. ["user.email", "token"]) applied to every event before egress

A POST /api/siem/destinations/:id/test endpoint issues a synthetic health_check event to verify connectivity before enabling the destination.

Adapter config shapes

Splunk HEC

{
"kind": "splunk",
"mode": "both",
"config_json": {
"endpoint": "https://hec.example.com",
"index": "sigmashake",
"sourcetype": "sigmashake:audit"
},
// credentials: { "hec_token": "…" }
}

Cribl HTTP-in

{
"kind": "cribl",
"mode": "both",
"config_json": {
"endpoint": "https://in.logstream.<tenant>.cribl.cloud:10080",
"path": "/cribl",
"source_label": "sigmashake-fleet"
},
// credentials: { "bearer_token": "…" }
}

Cribl Cloud ingest ports 443, 8088, 10080, and 10200 are permitted. HEC-compatible Cribl sources can use the splunk adapter instead.

Confluent Cloud Kafka REST v3

{
"kind": "confluent_rest",
"mode": "both",
"config_json": {
"endpoint": "https://pkc-xxxx.us-east-1.aws.confluent.cloud",
"cluster_id": "lkc-abcdef",
"topic": "sigmashake-audit"
},
// credentials: { "api_key": "…", "api_secret": "…" }
}

The credentials must be a cluster-scoped Kafka API key (created via the Confluent Cloud UI under your cluster's "API Keys" tab, or confluent api-key create --resource <lkc-id>), not a Confluent Cloud control-plane API key. Records are keyed by org_id, so all events for a single tenant land on the same Kafka partition and downstream consumers can rely on per-tenant ordering.

The adapter uses Confluent's streaming-produce response format in strict mode: if any per-record error_code in the response is ≥ 400, the whole batch is marked failed (retryable for 429/5xx, operator-fix for other 4xx). This avoids silent data loss on partial failures.

Generic webhook

{
"kind": "generic_webhook",
"mode": "both",
"config_json": {
"endpoint": "https://siem.example.com/ingest",
"max_bytes": 1000000
},
// credentials: { "hmac_secret": "…" }
}

Every request is signed as X-SigmaShake-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256(t + "." + body, secret)>. Receivers should verify the timestamp is within 5 minutes of now and recompute the HMAC before processing.

Credential handling

Credentials are never stored in plaintext:

  • The Fleet Worker holds an SIEM_DEK_MASTER secret (32-byte AES key, rotatable via SIEM_DEK_MASTER_V2).
  • On destination create/update, the credential object is serialized, encrypted with AES-GCM (random 12-byte IV), and only the ciphertext + IV + key version are persisted.
  • Decryption happens in-Worker at delivery time. Credentials are never logged and never appear in last_error output.
  • Bearer/API tokens pass a CRLF-injection check (validateHeaderValue) before being placed in an Authorization header.

Network security

All adapters enforce the same egress controls:

  • HTTPS only. HTTP endpoints are rejected at config-write time.
  • SSRF defence. Endpoint hostnames are validated against private IPv4 ranges (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16) and IPv6 loopback / ULA / link-local ranges. localhost and 0.0.0.0 are always blocked.
  • Port allowlists. Splunk/webhook/Confluent are pinned to port 443. The Cribl adapter additionally permits 8088/10080/10200 for Cribl Cloud ingest.
  • redirect: 'manual'. No adapter follows 3xx redirects — a redirect from the SIEM is treated as a misconfiguration and fails closed.
  • DNS-rebind re-validation. Each adapter re-runs its endpoint validator at delivery time, not just at config write time.

Observability

Each destination row exposes last_success_at, last_error, and last_error_at. A per-batch fleet_siem_delivery_log row records attempt count, status, Merkle root, destination-specific reference (e.g. HEC ackId, Kafka offset), and timing. Delivery-log rows are auto-purged after 90 days.

Redaction

The optional redact_fields_json array accepts dotted-path field selectors. During delivery, the event payload is deep-copied and matching fields are replaced with the literal string [REDACTED] before the adapter serializes the batch. This runs before chunking, so the outgoing wire format never contains the original value.

Adding a new adapter kind

The adapter model is pluggable. Implementers provide a Destination with validateConfig, deliver, and healthCheck, then register it in src/lib/siem/registry.ts. The forwarder and archiver have zero knowledge of concrete adapter kinds.