Skip to content

Audit trail

Every change of access and every instruction to a scanner:

  • who was given, changed or lost access to what — grants, links, folders restricted, members and teams;
  • who changed the organization’s policy, vocabulary, plan, domains, SSO and SCIM;
  • who told an agent to sweep, who changed what it sweeps, paused or deleted it, and what happened to API keys.

Each entry names who did it, what it concerned, and when. Entries are append-only. Reads are not recorded.

Who reads it What
Every member The agents’ activity: agents and keys
Admins The whole trail

The whole trail is for admins because it says who was let into restricted folders.

Admins export the trail as CSV or JSON for any window of up to a year (30 days by default), ten times a minute at most. Each export is itself recorded before the first byte is sent. In CSV, cells that a spreadsheet would read as a formula are prefixed with '.

Set in the security policy: 30 to 3650 days, or keep everything. Older entries are deleted daily.

Send every new entry, as it happens, to your own system. Admins add up to five streams; creating, changing, rotating and removing one asks for a second factor.

Delivery is at least once: events at least five seconds old are sent in order, up to 100 per batch, and a stream moves on only when the receiver accepted. Deduplicate on the event id. Failures back off, doubling up to six hours; after 30 consecutive failures the stream is turned off and that is recorded. Send test event sends one made-up event.

An HTTPS endpoint receiving POST requests:

{ "deliveryId": "…", "events": [{ "id": "…", "action": "access.expire", "…": "…" }] }

Each request is signed with the stream’s secret, shown once at creation:

X-Infrality-Signature: t=1761300000,v1=5f2b…

v1 is the hex HMAC-SHA256 of t, a full stop, and the raw body. Verify it before trusting the body, and refuse a t more than five minutes from your clock:

verify.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, header, rawBody, now = Date.now() / 1000) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(now - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(v1, 'hex');
return given.length === expected.length && timingSafeEqual(given, expected);
}

RFC 5424 messages, facility 13, octet-counted over TLS, to a host and port you give. The host must have a public address.