SIEM Export

Stream your enterprise audit log into your SIEM. Infracodebase exposes a pull API: a paginated JSON feed with strong delivery guarantees. We ship a ready-made poller for Splunk. Any SIEM that polls HTTPS works the same way.

You need

  • An Enterprise Access Token with enterprise.audit.read. Create it under Settings → Tokens with the SIEM / audit export template. The token appears once. Store it in your secret manager.
  • Your enterprise ID. Copy it from Settings → General.
  • Network access from your Splunk infrastructure to app.infracodebase.com. Single-tenant deployments: allow your dedicated gateway IP.

Your export endpoint:

https://app.infracodebase.com/api/enterprises/<ENTERPRISE_ID>/audit-log/export

Splunk Cloud or Splunk Enterprise?

The poller runs as a Splunk scripted input. Where it runs depends on your Splunk:

  • Splunk Enterprise (self-managed): install the input on your Splunk server. Follow the steps as written.
  • Splunk Cloud: Splunk Cloud does not run customer scripted inputs. Run the input on a heavy forwarder you manage, forwarding to your cloud stack. The steps stay the same; placement differs:
    • Steps 1-3 and the index-time half of step 4 go on the forwarder.
    • The search-time half of step 4 goes on your search head as a small private app.
    • Create the index (step 5) in Splunk Cloud under Settings → Indexes.

Direct push to Splunk HEC is on our roadmap and removes the forwarder requirement. Ask your account contact for timing.

Setup

The steps use the built-in search app. Substitute your own app if you have one. Paths in inputs.conf resolve against the app directory.

1. Install the poller

APPBIN="$SPLUNK_HOME/etc/apps/search/bin/scripts"
mkdir -p "$APPBIN"
curl -fsSL -o "$APPBIN/infracodebase_audit.py" \
  https://app.infracodebase.com/integrations/splunk/infracodebase_audit.py
chmod +x "$APPBIN/infracodebase_audit.py"

The script uses only the Python 3 standard library. No pip. Put it in the app's bin/scripts, not $SPLUNK_HOME/bin/scripts. Scripted-input paths resolve against the app, not $SPLUNK_HOME.

2. Add a wrapper with your config

Splunk silently ignores environment.* keys in inputs.conf for scripted inputs, so pass config through a wrapper. Create $APPBIN/infracodebase_audit.sh:

#!/bin/bash
export INFRACODEBASE_AUDIT_ENDPOINT="https://app.infracodebase.com/api/enterprises/<ENTERPRISE_ID>/audit-log/export"
export INFRACODEBASE_API_TOKEN="<YOUR_TOKEN>"
exec /usr/bin/python3 "$(dirname "$0")/infracodebase_audit.py"
chmod +x "$APPBIN/infracodebase_audit.sh"

Optional: export INFRACODEBASE_SINCE="2026-04-01T00:00:00Z" starts the feed at a timestamp instead of the beginning of history. Applies only before the first cursor exists.

In production, read the token from Splunk's credential store (storage/passwords) or a permission-restricted file. Do not inline it.

3. Configure the input

$SPLUNK_HOME/etc/apps/search/local/inputs.conf:

[script://./bin/scripts/infracodebase_audit.sh]
disabled = false
index = infracodebase_audit
interval = 300
sourcetype = infracodebase:audit

4. Extract fields

$SPLUNK_HOME/etc/apps/search/local/props.conf. On a single Splunk Enterprise instance, use the whole block. On Splunk Cloud, put the index-time half on the forwarder and the search-time half on the search head.

[infracodebase:audit]
# --- index-time (forwarder on the Splunk Cloud path) ---
SHOULD_LINEMERGE = false
LINE_BREAKER = ([\r\n]+)
TIME_PREFIX = "timestamp":\s*"
TIME_FORMAT = %Y-%m-%dT%H:%M:%S.%3NZ
# The trailing Z in TIME_FORMAT is a literal, not a timezone directive.
# Without TZ = UTC, Splunk assumes indexer-local time and skews _time.
TZ = UTC

# --- search-time (search head on the Splunk Cloud path) ---
KV_MODE = json
FIELDALIAS-user = actorEmail AS user
FIELDALIAS-src = ipAddress AS src
FIELDALIAS-object = resourceLabel AS object
FIELDALIAS-object_type = resourceType AS object_type
EVAL-app = "infracodebase"

5. Create the index and restart

$SPLUNK_HOME/bin/splunk add index infracodebase_audit -auth <user>:<pass>
$SPLUNK_HOME/bin/splunk restart

Splunk runs the input on startup, then every interval. Each run drains the entire backlog: it pages until hasMore=false and backs off on 429. The feed never falls behind, regardless of volume.

Verify

index=infracodebase_audit | head 5 | table _time action status user object src

Denied access attempts:

index=infracodebase_audit status=denied | table _time action user object src detail

Delivery guarantees

The platform enforces these:

  • Complete. A monotonic sequence keys every event. The export withholds events younger than 15 seconds, so an in-flight write can never land behind a cursor that already passed it. Every committed event exports exactly where its sequence falls.
  • Ordered. Pages ascend strictly by sequence. The cursor always points past the last returned event, including on the final page, so a caught-up poll returns no duplicates.
  • At-least-once. A crashed consumer re-reads at most one page. Deduplicate on id.
  • Tamper-evident. The database enforces append-only on the audit log. It rejects updates always. Deletion occurs only when you delete the enterprise itself.
  • Retained. Events persist for the life of your enterprise. Recover from a SIEM outage of any length: resume from your last cursor, or re-bootstrap with since.
  • Fresh. New events become exportable after ~15 seconds, well below any practical polling interval.

Limits: limit max 1000 per page, 100 requests/min/enterprise.

Event fields

FieldSplunk (after props)Description
timestamp_timeEvent time, ISO 8601 UTC
actionactionWhat happened, e.g. member.invite
statusstatussuccess / failure / denied
actorEmailuserWho did it
resourceTypeobject_typeKind of resource affected
resourceLabelobjectHuman-readable resource name
ipAddresssrcSource IP
detaildetailHuman-readable summary
idUnique event ID (dedupe key)

Troubleshooting

  • No events, or "errors" in splunkd.log. Splunk's ExecProcessor logs the poller's INFO/WARN diagnostics at ERROR level. That is normal. Test the wrapper directly; it prints one JSON object per line:
    "$APPBIN/infracodebase_audit.sh"
    
  • Re-ingest from the beginning. Delete the cursor file next to the script:
    rm "$APPBIN/.infracodebase_cursor"
    
  • 429 responses. The API allows 100 requests/min/enterprise. The poller backs off automatically. If 429s persist, raise interval.
  • 401/403. Verify the token holds enterprise.audit.read and belongs to the enterprise in the URL. Denied attempts appear in your own audit log as audit.export.read with status=denied.

Security recommendations

  • Use a dedicated token holding only enterprise.audit.read. The SIEM template does this.
  • Set an expiry and rotate on your credential schedule.
  • Store the token in Splunk's credential store, not in the wrapper script.
  • Alert on index=_internal source=*splunkd.log* "infracodebase_audit" ERROR plus an absence-of-events check.