Skip to content

Send security events to your SIEM

Shield360 sends its security events to the tools your security team already works in. Guard blocks and redactions, the audit log, sign-ins (including failed ones), shadow-AI network observations and agent gateway denials are normalised to the Open Cybersecurity Schema Framework (OCSF 1.3.0) and delivered every minute.

What is never sent: prompt or response text, the value a guard found (only its type, token and keyed fingerprint, never any character of it), the file or command a guard stopped, an agent’s search text or tool arguments, API keys (only a keyed fingerprint token such as [key_hint:fp_3f9c2a1b], never any character of a key), and request bodies.

  • The Manage SIEM exports permission (integrations.siem.manage) to add and change destinations. View SIEM exports (integrations.siem.read) shows them and their health; the Compliance officer role has it.
  • Network reachability from Shield360 to the destination over HTTPS (or TLS for syslog). See Private networks for self-hosted deployments.
Open the settings

Go to Settings → Integrations → SIEM & security tools and choose Add destination.

Choose where events go

Pick the destination type and fill in its settings (see Destinations). Credentials are encrypted when saved and are never shown again.

Choose scope and events

Whole organisation sends every project’s events and the organisation’s audit log. This project sends only the current project’s environments. Then pick the event categories to send.

Optionally backfill

Set Also send events since to deliver history from a date (up to 400 days back). Leave it empty to start from now.

Test

Choose Send test event. A shield360.test Detection Finding arrives at the destination, or the page shows the destination’s error.

Each destination shows its health: the last successful run, lag behind the newest event, events sent in the last 24 hours, and the last error (with credentials removed). You can pause and resume a destination (events wait, nothing is lost), backfill it from a date, edit it, or remove it. Every change, test and backfill is recorded in Govern → Records → Audit log.

CategorySourceSent when
data_protectionGuard decisions from coding agents and the SDKA guard blocked, redacted or warned about sensitive data
auditThe audit logSomeone changed configuration, or read sensitive data
authenticationThe audit logSomeone signed in, failed to sign in, or was refused admission
shadow_aiCollector network observationsA non-browser process reached an AI provider directly
agent_gatewayThe agent gateway call logThe gateway refused an agent’s tool call it was not granted
agent_activityActivity provenanceEvery action an AI agent took: its type, target, privilege, result, policy decision, approval and cost
agent_sandboxNVIDIA OpenShell sandboxesWhat OpenShell sandboxes’ policies allowed and denied: connections, HTTP requests, SSH sessions, process launches and policy loads. The request path only where content capture is Full
ai_interactionAI interactionsEvery prompt a person submitted to an AI site in a managed browser: site, model, account type, prompt length, attachment types, kinds of sensitive data and the decision. Never the prompt or response text

Every destination receives the same OCSF event (Sentinel receives it as a column of a flat row; see below). Common fields:

FieldMeaning
metadata.uidStable event id (s360- + 32 hex). The same source record always has the same id; use it to deduplicate.
metadata.versionOCSF version, 1.3.0
metadata.product{ "name": "Shield360", "vendor_name": "Shield360" }
metadata.log_nameThe Shield360 category, e.g. data_protection
metadata.event_codeThe Shield360 kind, e.g. guard.blocked, auth.sign_in.failed
metadata.tenant_uidYour Shield360 organisation id
timeWhen it happened, epoch milliseconds
class_uid, activity_id, type_uidThe OCSF class and activity (see mapping)
severity_id, severity1 Informational, 2 Low, 3 Medium, 4 High
messageOne readable line
unmapped.shield360Shield360 context: category, kind, organisation_id, project_id, database_config_id, environment, and per-kind details

An example Detection Finding for a guard block:

{
"class_uid": 2004, "class_name": "Detection Finding",
"category_uid": 2, "category_name": "Findings",
"activity_id": 1, "activity_name": "Create", "type_uid": 200401,
"time": 1759060800123, "severity_id": 4, "severity": "High",
"action_id": 2, "action": "Denied", "disposition_id": 2, "disposition": "Blocked",
"message": "Guard blocked api_key for ana on ana-mac",
"metadata": { "version": "1.3.0", "uid": "s360-9f2c…", "product": { "name": "Shield360", "vendor_name": "Shield360", "uid": "shield360" },
"log_name": "data_protection", "event_code": "guard.blocked", "tenant_uid": "org_…" },
"finding_info": { "uid": "s360-9f2c…", "title": "Sensitive data blocked by a guard", "types": ["api_key"],
"analytic": { "name": "shield360-guard", "type_id": 1, "type": "Rule" } },
"actor": { "user": { "name": "ana" }, "app_name": "claude-code" },
"device": { "hostname": "ana-mac", "os": { "name": "darwin" } },
"evidences": [ { "data": { "kind": "api_key", "masked": "[api_key:fp_01234567]", "fingerprint": "0123456789abcdef" } } ],
"unmapped": { "shield360": { "category": "data_protection", "kind": "guard.blocked", "source": "coding_agent", "tool": "Bash", "fingerprints": ["0123456789abcdef"] } }
}
Shield360 eventkindOCSF class (class_uid)ActivityKey fieldsSeverity
Guard blockedguard.blockedDetection Finding (2004)Create (1)action Denied, disposition Blocked, finding_info.types = data kinds, evidences[].data = type, token and fingerprint (plus vault_ref when it matches a vault secret)High
Guard redactedguard.redactedDetection Finding (2004)Create (1)action Modified, disposition CorrectedMedium
Guard warnedguard.warnedDetection Finding (2004)Create (1)action Observed, disposition DetectedLow
Configuration changeaudit.change.<outcome>API Activity (6003)Create (1) / Update (3) / Delete (4) / Other (99)api.operation = audit action, actor.user, src_endpoint.ip, http_request, resources[], statusInformational; Medium if denied; Low if failed
Read of sensitive dataaudit.access.<outcome>API Activity (6003)Read (2)as aboveas above
Sign-inauth.sign_in.succeeded / auth.sign_in.failedAuthentication (3002)Logon (1)user, src_endpoint.ip, http_request.user_agent, status, unmapped.shield360.details.reasonInformational; Medium if failed
Other auth events (e.g. admission refused)auth.<action>.<outcome>Authentication (3002)Other (99)as aboveas above
Process reached an AI providershadow_ai.networkNetwork Activity (4001)Traffic (6)actor.process (name, path, pid, user), device.hostname, dst_endpoint.svc_name = provider, start_time/end_time, count = samples, unmapped.shield360.key_hints (provider, variable and fingerprint token; no characters of a key)Low
Agent gateway denialagent_gateway.deniedDetection Finding (2004)Create (1)action Denied, disposition Blocked, actor.app_name/app_uid = agent, api.operation = gateway method, resources[] = toolMedium
Agent actionagent_activity.<action type>API Activity (6003)Other (99)actor.app_name = agent, actor.user.name = initiating person, api.operation = action type, api.service.name = tool, src_endpoint.hostname, dst_endpoint.hostname = target host, resources[] = target, status/status_code; tokenized action, privilege, approval and cost under unmapped.shield360Medium when denied or admin/root; Low when failed or elevated; else Informational
OpenShell sandbox decisionopenshell.<class>.<decision>, openshell.config.policy_changedThe class OpenShell wrote it as: Network Activity (4001), HTTP Activity (4002), SSH Activity (4007), Process Activity (1007), Device Config State Change (5019), Detection Finding (2004)OpenShell’s activity (Open, Refuse, the HTTP method, Launch, Log)action/disposition (Allowed, Denied/Blocked), status_detail = reason code, dst_endpoint.hostname/port, http_request.http_method and url (path only if stored), actor.process.file.path = binary, actor.app_name = agent, firewall_rule.name/type = policy rule and engine, container.uid/name = sandbox, device.hostname; OpenShell version, source OCSF version, workspace and policy hash under unmapped.shield360. Never the message, a command line, a query string, a body or a header — Shield360 never stored themOpenShell’s severity (Critical and Fatal as High); else Medium when denied
AI interactionai_interaction.<decision>API Activity (6003)Create (1)actor.user.name, actor.app_name = app, api.operation = prompt, api.service.name and dst_endpoint.hostname = site, src_endpoint.hostname = browser, status Success when sent; site, account type, model, prompt length, attachment types, kinds, decision and content_stored under unmapped.shield360Medium when blocked; Low when sent with sensitive data or sent anyway after a warning; else Informational
Test eventshield360.testDetection Finding (2004)Create (1)finding_info.title “Shield360 test event”Informational
  • At-least-once. Each stream (the audit log, and each environment’s guard events, network observations and gateway denials) keeps a cursor. The cursor moves only after the destination accepted a batch. A refused batch is sent again, with the same event ids, so deduplicate on metadata.uid.
  • Batches are at most 500 events, split further to respect each destination’s request size limit. A run sends at most 20 batches per destination; what does not fit shows as lag and goes in the next minute.
  • Retries. Timeouts, 429 and 5xx are retried three times per batch with exponential backoff and jitter. If the batch still fails, the destination waits 1, 2, 4 … up to 30 minutes between runs. A refused credential or a blocked address is not retried until the configuration changes.
  • Late events. Collector events are exported by when they reached Shield360, so a laptop that comes back online still has its events exported, as long as they arrive within 7 days of happening.
  1. In Splunk, Settings → Data inputs → HTTP Event Collector → New token.
  2. Give the token a name, leave Enable indexer acknowledgement off, and under Input settings restrict Allowed indexes to the index that should receive Shield360 events (for example security). Set the source type to shield360:ocsf or leave it automatic.
  3. In Shield360 enter the HEC URL (the base, for example https://splunk.example.com:8088, or https://http-inputs-<stack>.splunkcloud.com for Splunk Cloud), the token, and optionally the index.

Events arrive at /services/collector/event with Authorization: Splunk <token>, the OCSF record as event, and indexed fields shield360_event_id, shield360_category, shield360_organisation_id and shield360_project_id.

index=security sourcetype="shield360:ocsf" metadata.log_name=data_protection severity_id>=4
| dedup metadata.uid

Shield360 uses the Logs Ingestion API: an Entra application writes through a data collection endpoint (DCE) and a data collection rule (DCR) into a custom table.

Register an application

In Entra ID, register an application and create a client secret. Note the tenant ID, application (client) ID and the secret.

Create a data collection endpoint
Terminal window
az monitor data-collection endpoint create \
--resource-group security-rg --name shield360-dce \
--location eastus --public-network-access Enabled

Note its logs ingestion URL, for example https://shield360-dce-abcd.eastus-1.ingest.monitor.azure.com.

Create the table

In the Sentinel workspace, create the custom table Shield360Security_CL:

Terminal window
az monitor log-analytics workspace table create \
--resource-group security-rg --workspace-name sentinel-ws \
--name Shield360Security_CL --plan Analytics --columns \
TimeGenerated=datetime EventId=string Category=string Kind=string \
Severity=string Summary=string ClassUid=int ClassName=string \
ActivityName=string TypeUid=long SeverityId=int UserName=string \
DeviceHostname=string OrganisationId=string ProjectId=string \
Environment=string Ocsf=dynamic
Create the data collection rule

Save as dcr.json, replacing the placeholders, and create it with az monitor data-collection rule create --resource-group security-rg --name shield360-dcr --location eastus --rule-file dcr.json:

{
"properties": {
"dataCollectionEndpointId": "/subscriptions/<sub>/resourceGroups/security-rg/providers/Microsoft.Insights/dataCollectionEndpoints/shield360-dce",
"streamDeclarations": {
"Custom-Shield360Security_CL": {
"columns": [
{ "name": "TimeGenerated", "type": "datetime" },
{ "name": "EventId", "type": "string" },
{ "name": "Category", "type": "string" },
{ "name": "Kind", "type": "string" },
{ "name": "Severity", "type": "string" },
{ "name": "Summary", "type": "string" },
{ "name": "ClassUid", "type": "int" },
{ "name": "ClassName", "type": "string" },
{ "name": "ActivityName", "type": "string" },
{ "name": "TypeUid", "type": "long" },
{ "name": "SeverityId", "type": "int" },
{ "name": "UserName", "type": "string" },
{ "name": "DeviceHostname", "type": "string" },
{ "name": "OrganisationId", "type": "string" },
{ "name": "ProjectId", "type": "string" },
{ "name": "Environment", "type": "string" },
{ "name": "Ocsf", "type": "dynamic" }
]
}
},
"destinations": {
"logAnalytics": [
{ "workspaceResourceId": "/subscriptions/<sub>/resourceGroups/security-rg/providers/Microsoft.OperationalInsights/workspaces/sentinel-ws", "name": "sentinel" }
]
},
"dataFlows": [
{
"streams": ["Custom-Shield360Security_CL"],
"destinations": ["sentinel"],
"transformKql": "source",
"outputStream": "Custom-Shield360Security_CL"
}
]
}
}

Note the rule’s immutable ID (dcr-…), shown with az monitor data-collection rule show --name shield360-dcr --resource-group security-rg --query immutableId.

Grant the application access

Assign the application the Monitoring Metrics Publisher role on the DCR:

Terminal window
az role assignment create --assignee <client-id> \
--role "Monitoring Metrics Publisher" \
--scope $(az monitor data-collection rule show --name shield360-dcr --resource-group security-rg --query id -o tsv)
Add the destination

Enter the DCE URL, DCR immutable ID, stream name Custom-Shield360Security_CL, tenant ID, client ID and secret.

Role assignments can take a few minutes to apply; until then the test event fails with 403. Query the table with KQL:

Shield360Security_CL
| where Category == "authentication" and Kind endswith ".failed"
| summarize failures = dcount(EventId) by UserName, bin(TimeGenerated, 1h)

US Government (*.ingest.monitor.azure.us) and China (*.azure.cn) DCEs use their own Entra authority automatically.

Create an API key (Settings → Developer → Shield360 API keys) and choose your Datadog site. Logs arrive with source:shield360, service:shield360 (or your choice), tags shield360_category, severity and env, evt.id set to the stable event id, and the OCSF record under ocsf.

Create an API key with the create_doc privilege on the target index or data stream, and enter the encoded key (base64 of id:api_key, as Kibana shows it), the Elasticsearch URL and the index (default shield360-security). Events are written with _bulk using create and the stable event id as _id, so a resent event is ignored as a conflict rather than duplicated. Documents carry @timestamp, event.id, event.category, message and the OCSF record under ocsf.

POST /_security/api_key
{
"name": "shield360-siem",
"role_descriptors": {
"shield360_writer": { "indices": [ { "names": ["shield360-security*"], "privileges": ["create_doc", "auto_configure"] } ] }
}
}

Shield360 POSTs application/x-ndjson (one OCSF event per line) to your URL with these headers:

HeaderValue
Shield360-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
Shield360-DeliveryA unique id for the request
Shield360-Event-CountEvents in the body

The signature is HMAC-SHA256(signing secret, "<t>.<raw body>"). Verify it over the raw body, compare in constant time, and refuse deliveries whose t is more than five minutes from your clock. Answer 2xx once stored; anything else is retried.

import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret: string, rawBody: string, header: string, now = Date.now() / 1000): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2) as [string, string]));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const given = String(parts.v1 || "");
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
import hashlib, hmac, time
def verify(secret: str, raw_body: bytes, header: str, now: float | None = None) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs((now or time.time()) - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))

The signing secret must be at least 32 characters. Generate in the form creates one; copy it before saving, because it is never shown again.

Shield360 writes gzip-compressed NDJSON into your bucket, one object per batch and day:

<prefix>year=2026/month=09/day=28/shield360-20260928T120000Z-<hash>.ndjson.gz

Object names are derived from the events they contain, so a retried batch overwrites its own object. Shield360 assumes a role in your account with an external ID it generates for the destination, and checks the bucket is owned by the role’s account.

Add the destination

Enter the bucket, region, an optional prefix, and the ARN of the role you are about to create, for example arn:aws:iam::123456789012:role/Shield360SiemExport.

Create the role

Choose AWS setup on the destination. It shows the external ID and a trust policy naming the Shield360 role, shaped like this:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "<Shield360 role ARN shown in the app>" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "<external ID shown in the app>" } }
}
]
}

Create the role with that trust policy and this permissions policy:

{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::acme-security-logs/shield360/*" }
]
}

If the bucket uses SSE-KMS with a customer-managed key, also allow kms:GenerateDataKey on that key.

Test

Choose Send test event and look for the object under today’s date.

Point Amazon Security Lake, Athena or your SIEM’s S3 input at the prefix.

For on-premises SIEMs (QRadar, ArcSight, syslog-ng, rsyslog). Shield360 opens a TLS 1.2+ connection (default port 6514), always verifies the server certificate (add your CA in PEM if it is private), and sends RFC 5424 messages with octet-counting framing (RFC 5425), facility 13 (log audit):

<107>1 2026-09-28T12:00:00.123Z shield360 shield360 - data_protection - CEF:0|Shield360|Shield360|1.0|guard.blocked|Guard blocked api_key for ana on ana-mac|8|rt=1759060800123 externalId=s360-9f2c… cat=data_protection act=Denied suser=ana shost=ana-mac …

Choose CEF (extensions rt, externalId, cat, act, outcome, suser, shost, msg, cs1 organisation, cs2 project, cs3 environment, cn1 OCSF class) or JSON (the OCSF record as the message).

Destinations must resolve to public addresses: private (RFC 1918), loopback, link-local and unique-local addresses are refused, every redirect is checked again, and syslog connects to the address that was checked. Cloud metadata addresses are always refused.

A self-hosted deployment whose SIEM is on the same private network can allow it by setting, on the Shield360 server:

Terminal window
SIEM_EXPORT_ALLOW_PRIVATE_NETWORK=true

This also permits plain http:// URLs. It has no effect in the hosted (cloud) edition.