Audit Logs
SubImage records who did what, when, and whether it succeeded. The trail is queryable from Settings > Audit Trail and readable through the API, and it can be forwarded to your own SIEM.
Reading the trail requires an admin user.
What Is Recorded
Every action that changes something, and every action that fails or is denied:
- mutations: anything that is not a read, on the API and in the product UI
- denials and failures: an unauthorized call, a rejected request, a server error
- sensitive reads: reading credentials or secret material, running arbitrary Cypher, and bulk data exports
- agent activity: every tool call made by the chatbot or over MCP, including the query text an agent ran
- graph access outside SubImage: Neo4j Browser sessions and driver connections that use a Neo4j API key, recorded from the database's own logs
Ordinary reads are deliberately not recorded. One dashboard page load fans out across many API calls, which is where the volume would come from and not the signal.
What Each Event Carries
- actor: who acted, how they authenticated, and their role at the time
- action: what was attempted
- resource: what it was attempted on
- outcome: success, failure, or denied
- request context: method, route, status, source IP, user agent
- what changed: field by field, for actions that changed something
- what was read: query text and row count, for query and agent tool paths
Secret values are never recorded. A field that holds a credential is marked as changed without its value, so the trail shows that a key was replaced and never what it was replaced with.
Retention
SubImage retains 90 days, immediately queryable. That is what PCI DSS 4.0 §10.5.1 asks to be available without restore, and it is shorter than the 12 months the same requirement asks for overall.
Retention beyond 90 days is a forwarding destination you configure, not an archive on the SubImage side: the copy that outlives the window lives in infrastructure you own.
Forwarding to Your Own SIEM
An export destination forwards every recorded event to a system you control, which is both how you keep the trail past 90 days and how your own detections run on it. Configure one from Settings > Audit Trail.
Creating and enabling an export requires an admin user, the same level the trail itself requires: forwarding it is a standing copy of it.
Three behaviours are worth knowing before you enable one:
- Forwarding starts when you enable the export. It does not backfill events recorded before that, so anything already in the trail stays reachable through the API only.
- A destination that stops accepting holds its place. If your API key is revoked or the intake rejects a batch, SubImage stops advancing and resumes from the same event once the problem is fixed, rather than skipping ahead. An outage therefore delays the copy instead of putting a hole in it.
- An outage longer than the retention window loses what it did not carry. Nothing here is an archive, so events that age past 90 days without being forwarded are gone.
Two destinations are available: Datadog, for detections that run in your SIEM, and Amazon S3, for retention past 90 days in storage you own.
Datadog
SubImage sends each event to Datadog's HTTP log intake every five minutes, in the order the events were recorded, and sends nothing else: no metrics, no traces, and no data from your graph.
Each log arrives with ddsource:subimage and service:subimage-audit, and the
event nested under an audit attribute, so its fields are addressable as facets
(@audit.actor.subject_id, @audit.action.type, @audit.outcome).
In Datadog, with a user who can create API keys, which usually means Admin:
- Open Organization Settings > API Keys and click New Key. Name it
something you will recognize later, such as
subimage-audit-export, and copy the value: Datadog shows it once. - Note your Datadog site, which is the domain in your Datadog URL.
app.datadoghq.commeans the site isdatadoghq.com,app.datadoghq.eumeansdatadoghq.eu,us3.datadoghq.commeansus3.datadoghq.com. SubImage sends to the intake for that site, so the wrong one is rejected.
An API key is all that is required. SubImage only writes logs, so it needs no application key alongside it.
In SubImage, from Settings > Audit Trail:
- In the Export section, click New export, name it, pick your site, and paste the API key.
- Save. The export is created disabled, so nothing is sent yet.
- Turn it on with its toggle.
The API key is stored in a vault and is never shown again, including to you. Editing an export leaves the key field empty on purpose: submit without it to keep the stored key, or paste a new one to rotate it.
To verify, query service:subimage-audit in Datadog's Logs > Search. The
first events arrive within about five minutes. If you see nothing, do something in
SubImage that gets recorded, such as opening the audit trail itself, then look
again on the next interval.
One Datadog-specific caveat. Datadog only accepts an event dated within the
last 18 hours. After a longer outage SubImage still sends the backlog, but Datadog
stamps those logs with their arrival time. The original time is preserved in the
payload, so filter on @audit.timestamp rather than the log date when reading a
period that was delayed.
Amazon S3
SubImage writes the trail into a bucket in your own AWS account, every five minutes, as gzipped NDJSON: one JSON object per line, in the order the events were recorded. Access is by cross-account role assumption, so SubImage never holds a credential of yours for this and you can revoke it at any time by deleting the role.
Objects are laid out as <your prefix>/dt=YYYY-MM-DD/<first event>.ndjson.gz. An
object is named after the first event it holds, and event keys begin with a
timestamp, so a plain listing is already in chronological order and the dt=
prefix is directly usable as a partition in Athena or Glue. If a delivery is
interrupted after SubImage has sent it but before it knows the result, the retry
rewrites that same object rather than adding a second copy beside it.
In SubImage first, from Settings > Audit Trail: open New export and pick Amazon S3. The form names the role to create and shows the two values it must trust: the principal (the IAM role SubImage runs as) and an external ID that SubImage assigns to your deployment. Copy them before continuing.
In AWS, in the account that owns the bucket:
Create an IAM role named exactly as the form gives it, normally
SubImageAuditExportRole. The name is fixed: SubImage is only permitted to assume roles with that name, which is what keeps the permission narrow.Give it this trust policy, with the two values from the form:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "<principal from the form>" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "<external ID from the form>" } } } ] }Attach a permissions policy scoped to the prefix you intend to use, and nothing else:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:PutObject", "s3:PutObjectAcl"], "Resource": "arn:aws:s3:::<your bucket>/<your prefix>/*" } ] }s3:PutObjectAclis there because every object is written with thebucket-owner-full-controlACL, which is what makes your account the owner of the objects rather than SubImage. Without it the write is refused.If the bucket is encrypted with a customer-managed KMS key, the role also needs
kms:GenerateDataKeyon that key.
Back in SubImage, fill in the bucket as s3://your-bucket/your-prefix, the
12-digit account ID, and the bucket's region. The region must be the bucket's own:
a write sent to the wrong regional endpoint is refused rather than redirected.
Save, then turn the export on with its toggle.
Object ownership. SubImage writes every object with the
bucket-owner-full-control ACL, so your account owns the trail whichever Object
Ownership setting the bucket uses. On a bucket left on the legacy Object writer
setting this is what keeps the objects readable by you; on a bucket using the
default Bucket owner enforced setting the ACL is accepted and changes nothing,
since you already own everything written there.
We recommend enabling S3 Object Lock in GOVERNANCE mode on the bucket, with a retention period matching your compliance obligation. SubImage only ever writes new objects and never deletes one, but Object Lock is what makes that a property of the bucket rather than a promise of ours.
To verify, list the prefix a few minutes after enabling. If it is empty, do something in SubImage that gets recorded, such as opening the audit trail itself, then look again on the next interval.
Reading the Trail Programmatically
GET /api/audit/events serves the trail newest first, as a cursor-paginated
stream. Filters are actor id, actor email, action type, and a time window.
Treat the cursor, not the page size, as the signal for whether more events exist: a short or empty page that still carries a cursor means keep going.