Skip to content

v0.30.0

Release Date: 2026-10-29

A release that writes SystemForge's security events as OCSF:

  • OCSF security events. contract/audit/ocsfaudit maps every security event to an OCSF 1.10.0-dev event class and writes it as one JSON object per line to a writer the host names, typically a file opened for appending, for a security data lake or SIEM that ingests OCSF.
  • Several emitters at once. audit.NewMultiEmitter hands every event to each of its emitters, so a deployment can keep the log of security events and write OCSF too.

It also sanitizes the client's User-Agent in every security event, in the log as well as in OCSF, leaving it out when it looks like it holds an email address or a credential.

It adds no breaking change. Nothing is written as OCSF until a host passes the emitter, for example as systemauthsvc.Options.AuditEmitter.

Highlights

  • Every event type mapped. Sign-ins, sign-outs, token refusals, revocations and replays are Authentication events; account, email and privilege changes are User Management; organization membership is Group Management; keys, connections, SCIM tokens, sign-up codes and invites are Entity Management; signing-key loading and production waivers are Application Lifecycle. An event type SystemForge adds later is still written, as an OCSF Base Event. The mapping lists every class and activity.
  • Valid OCSF. A test validates the output of every event type, with success and failure variants, strictly against the schema embedded in github.com/grokify/ocsf-schema-go. Events carry the schema version 1.10.0-dev in metadata.version, the development version of OCSF 1.10; check that a destination accepts it.
  • Fields a lake can filter on. The deployment's name is in metadata.reporter.name, the organization in metadata.tenant_uid, the event type in metadata.event_code and the request ID in metadata.correlation_uid, beside the principal's ID, the client address and the user agent.

Security

  • Only IDs and addresses identify people. Names, email addresses, session tokens, tokens, token and code prefixes, codes, proofs and free-text reasons are left out of every field, unmapped included. A revoked managed-principal session is named by its record UUID in session.uid, which is no credential. Event details reach the output only through an allowlist for each event type, and only as codes, UUIDs, booleans, integers and times. The tests proving no token or secret reaches a log or event now run this emitter too.
  • The User-Agent is sanitized. A client can put anything in it, so audit.SanitizeUserAgent removes control characters, leaves out a User-Agent that then looks like it holds an email address or a credential, and cuts any other to 512 bytes. The OCSF emitter and the log emitter (audit.NewLogEmitter) both record only the sanitized value. The check is a heuristic for the common shapes, not a guarantee against every piece of personal data a client could send.
  • Severity follows the signal. A refresh token or authorization code replay is High severity with is_alert set, granting platform admin is High, and a production waiver at startup is Medium.

Delivery

  • Emit never blocks, never logs and never fails the request. It encodes the event and queues the line in a bounded buffer (1024 lines by default) that one goroutine writes.
  • When the buffer is full the event is dropped and counted (Dropped); a reporter goroutine logs the count once a minute and on Close, so a slow log destination cannot hold up a request. Write failures, short writes included, are counted (WriteFailures) and logged at most once a minute.
  • A Write that fails part-way, as on a full disk, loses only its own line: the next line starts on a new line, so every following line is whole JSON.
  • Close stops accepting events, writes those queued, flushes the writer and syncs a file. It does not close the writer.
  • Rotation is the host's job. Rotate by renaming, with a writer that reopens its file on SIGHUP, with postrotate waiting for the reopen and delaycompress when compressing; copytruncate can lose events written between its copy and its truncation.

See OCSF Security Events for wiring with an append-mode file and the full list of attributes.

Upgrading

Nothing changes for an existing deployment. To write OCSF events, open a file for appending, pass ocsfaudit.New(file, ocsfaudit.Options{...}) as the audit emitter (combined with the existing one through audit.NewMultiEmitter to keep both), and call Close on shutdown before closing the file.

The log emitter now leaves out a User-Agent that holds an email address or a credential-like value; log queries that matched such values in context.user_agent find nothing.

Dependencies

  • Adds github.com/grokify/ocsf-schema-go v0.2.0.