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.
Prerequisites
Section titled “Prerequisites”- 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.
Add a destination
Section titled “Add a destination”Go to Settings → Integrations → SIEM & security tools and choose Add destination.
Pick the destination type and fill in its settings (see Destinations). Credentials are encrypted when saved and are never shown again.
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.
Set Also send events since to deliver history from a date (up to 400 days back). Leave it empty to start from now.
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.
Event categories
Section titled “Event categories”| Category | Source | Sent when |
|---|---|---|
data_protection | Guard decisions from coding agents and the SDK | A guard blocked, redacted or warned about sensitive data |
audit | The audit log | Someone changed configuration, or read sensitive data |
authentication | The audit log | Someone signed in, failed to sign in, or was refused admission |
shadow_ai | Collector network observations | A non-browser process reached an AI provider directly |
agent_gateway | The agent gateway call log | The gateway refused an agent’s tool call it was not granted |
agent_activity | Activity provenance | Every action an AI agent took: its type, target, privilege, result, policy decision, approval and cost |
agent_sandbox | NVIDIA OpenShell sandboxes | What 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_interaction | AI interactions | Every 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 |
Event schema
Section titled “Event schema”Every destination receives the same OCSF event (Sentinel receives it as a column of a flat row; see below). Common fields:
| Field | Meaning |
|---|---|
metadata.uid | Stable event id (s360- + 32 hex). The same source record always has the same id; use it to deduplicate. |
metadata.version | OCSF version, 1.3.0 |
metadata.product | { "name": "Shield360", "vendor_name": "Shield360" } |
metadata.log_name | The Shield360 category, e.g. data_protection |
metadata.event_code | The Shield360 kind, e.g. guard.blocked, auth.sign_in.failed |
metadata.tenant_uid | Your Shield360 organisation id |
time | When it happened, epoch milliseconds |
class_uid, activity_id, type_uid | The OCSF class and activity (see mapping) |
severity_id, severity | 1 Informational, 2 Low, 3 Medium, 4 High |
message | One readable line |
unmapped.shield360 | Shield360 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"] } }}OCSF mapping
Section titled “OCSF mapping”| Shield360 event | kind | OCSF class (class_uid) | Activity | Key fields | Severity |
|---|---|---|---|---|---|
| Guard blocked | guard.blocked | Detection 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 redacted | guard.redacted | Detection Finding (2004) | Create (1) | action Modified, disposition Corrected | Medium |
| Guard warned | guard.warned | Detection Finding (2004) | Create (1) | action Observed, disposition Detected | Low |
| Configuration change | audit.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[], status | Informational; Medium if denied; Low if failed |
| Read of sensitive data | audit.access.<outcome> | API Activity (6003) | Read (2) | as above | as above |
| Sign-in | auth.sign_in.succeeded / auth.sign_in.failed | Authentication (3002) | Logon (1) | user, src_endpoint.ip, http_request.user_agent, status, unmapped.shield360.details.reason | Informational; Medium if failed |
| Other auth events (e.g. admission refused) | auth.<action>.<outcome> | Authentication (3002) | Other (99) | as above | as above |
| Process reached an AI provider | shadow_ai.network | Network 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 denial | agent_gateway.denied | Detection Finding (2004) | Create (1) | action Denied, disposition Blocked, actor.app_name/app_uid = agent, api.operation = gateway method, resources[] = tool | Medium |
| Agent action | agent_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.shield360 | Medium when denied or admin/root; Low when failed or elevated; else Informational |
| OpenShell sandbox decision | openshell.<class>.<decision>, openshell.config.policy_changed | The 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 them | OpenShell’s severity (Critical and Fatal as High); else Medium when denied |
| AI interaction | ai_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.shield360 | Medium when blocked; Low when sent with sensitive data or sent anyway after a warning; else Informational |
| Test event | shield360.test | Detection Finding (2004) | Create (1) | finding_info.title “Shield360 test event” | Informational |
Delivery guarantees
Section titled “Delivery guarantees”- 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.
Destinations
Section titled “Destinations”Splunk HTTP Event Collector
Section titled “Splunk HTTP Event Collector”- In Splunk, Settings → Data inputs → HTTP Event Collector → New token.
- 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 toshield360:ocsfor leave it automatic. - In Shield360 enter the HEC URL (the base, for example
https://splunk.example.com:8088, orhttps://http-inputs-<stack>.splunkcloud.comfor 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.uidMicrosoft Sentinel
Section titled “Microsoft Sentinel”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.
In Entra ID, register an application and create a client secret. Note the tenant ID, application (client) ID and the secret.
az monitor data-collection endpoint create \ --resource-group security-rg --name shield360-dce \ --location eastus --public-network-access EnabledNote its logs ingestion URL, for example
https://shield360-dce-abcd.eastus-1.ingest.monitor.azure.com.
In the Sentinel workspace, create the custom table Shield360Security_CL:
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=dynamicSave 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.
Assign the application the Monitoring Metrics Publisher role on the DCR:
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)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.
Datadog Logs
Section titled “Datadog Logs”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.
Elastic
Section titled “Elastic”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"] } ] } }}HTTPS webhook
Section titled “HTTPS webhook”Shield360 POSTs application/x-ndjson (one OCSF event per line) to your
URL with these headers:
| Header | Value |
|---|---|
Shield360-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Shield360-Delivery | A unique id for the request |
Shield360-Event-Count | Events 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.
Amazon S3
Section titled “Amazon S3”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.gzObject 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.
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.
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.
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.
Syslog over TLS
Section titled “Syslog over TLS”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).
Private networks
Section titled “Private networks”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:
SIEM_EXPORT_ALLOW_PRIVATE_NETWORK=trueThis also permits plain http:// URLs. It has no effect in the hosted
(cloud) edition.