splunk-add-on
CLAUDE.md0 starsChanged 3 months agoMITView on GitHub
A Splunk add-on that polls the PostHog activity logs API and indexes each entry as an OCSF event. It exists because Splunk Cloud does not let customers create scripted inputs, and the roles available to them do not include the capability to add one, so polling from Splunk Cloud requires a packaged add-on. Change globalConfig.json rather than anything under output/. UCC regenerates that directory on every build, so edits there vanish. The version lives in one place: meta.version in globalConfig.json.
The file
We split this CLAUDE.md into the rules it gives an agent
7 rules. About 0 are common, 4 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 33
Add a ## <version> section to CHANGELOG.md with bullet points, written for the person installing the add-on rather than for the person who …
- Distinctive: line 42
Every push and pull request builds and validates.
- Very specific: line 57
If you ever need solnlib.observability or splunklib.ai, remove the matching exclusions and expect the package to become platform specific …
- Distinctive: line 108
Do not weaken these without understanding what they prevent.
- Very specific: line 119
Never log the key, and never put it in inputs.conf.
- Distinctive: line 149
Run a Splunk container and install the built app.
- Distinctive: line 184
Keep that list in step with the supported range above, and never hand-edit the generated conf.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Working on this add-on
A Splunk add-on that polls the PostHog activity logs API and indexes each entry as an OCSF event. It exists because Splunk Cloud does not let customers create scripted inputs, and the roles available to them do not include the capability to add one, so polling from Splunk Cloud requires a packaged add-on.
Layout
TA_posthog_activity_logs/
globalConfig.json the whole UI, REST handlers and input wiring; the source of truth
package/bin/ the collection code and the account REST handler
package/lib/exclude.txt dependencies dropped from the build (see below)
package/default/ conf files merged into the generated app
output/ generated; never edit, never commitChange globalConfig.json rather than anything under output/. UCC regenerates that directory on
every build, so edits there vanish.
cd TA_posthog_activity_logs
ucc-gen build --source package --ta-version <version>
ucc-gen package --path output/TA_posthog_activity_logsReleasing
The version lives in one place: meta.version in globalConfig.json.
- Bump
meta.version. - Add a
## <version>section toCHANGELOG.mdwith bullet points, written for the person installing the add-on rather than for the person who wrote the code. - Tag
v<version>and push the tag.
CI builds, runs the Cloud vetting gate, attaches the .spl to a GitHub release, and uses the
matching CHANGELOG section as the release notes. It fails when the tag does not match
meta.version, or when CHANGELOG has no section for the version, so the notes cannot be
forgotten.
Every push and pull request builds and validates; only a tag publishes.
Things that will bite you
Keep the package pure Python
package/lib/exclude.txt drops gRPC, OpenTelemetry and PySocks. solnlib declares them for
solnlib.observability, and splunklib for splunklib.ai. Neither module appears in its package
__init__, and this add-on imports neither, so none of it is reachable. Dropping them took the
package from 9.0 MB to 1.9 MB and, more importantly, removed the last compiled extension.
A pure Python package installs on any platform. The moment something reintroduces a binary, the
build only works on the platform that produced it, and a macOS build silently fails to install on
Linux. CI asserts no .so, .pyd or .dylib survives the build.
If you ever need solnlib.observability or splunklib.ai, remove the matching exclusions and
expect the package to become platform specific again.
Python 3.9 is the floor, and that sets the Splunk floor
inputs.conf declares python.version = python3, meaning Splunk's default interpreter:
| Splunk | Default Python | Supported |
| 9.0 to 9.1 | 3.7 | no, solnlib needs 3.9 |
| 9.2 to 10.1 | 3.9 | yes |
| 10.2 and up | 3.9, 3.13 opt-in | yes |
So the add-on supports Splunk 9.2 and later. CI compiles the tree under 3.9 to hold that line.
splunk-sdk declares Requires-Python >=3.13, which looks disqualifying but is not: the only
3.13 syntax is in splunklib/ai, which nothing here imports. CI excludes that path from the 3.9
check for exactly this reason.
This add-on is not CIM
It emits OCSF, and there is no tags.conf, no eventtypes.conf and no CIM field aliases. Do not
claim a CIM version on the Splunkbase listing. It would tell users the events populate CIM data
models when they do not.
Splunk upgrades do not prune
splunk install app -update 1 overlays files. Dependencies removed in a new version stay on disk,
so an upgrade never reclaims the space a slimmer release promises. Only a clean install does. Say
so in release notes when a version removes something.
Do not override UCC's generated conf
Adding a restmap.conf stanza that UCC also generates replaces the generated one rather than
merging, which breaks the REST handlers and fails AppInspect. Extend globalConfig.json instead.
Likewise, do not set DATETIME_CONFIG = CURRENT in props.conf. It forces the index time and
overrides the event time the modular input sets, so every event lands with the same timestamp.
The checkpoint outlives the input
The position is stored by input name under the checkpoint directory. Deleting an input does not delete its checkpoint, so recreating an input with the same name resumes from the old position rather than collecting from the start. Use a new name when you want a fresh collection.
The checkpoint is a FileCheckpointer, deliberately not KVStoreCheckpointer. The position is a
single string per input and needs no replication, and a KV Store that fails to start would
otherwise stop collection entirely.
Security invariants
Do not weaken these without understanding what they prevent.
- Validate the paging link before indexing the page it came with. Every request carries the
API key, so a
nextlink is only safe to follow once it is known to point at the configured host. Validating after indexing also re-ingests the same page on every later run, because the run ends with no position saved. - Changing an account's host requires the key again. The REST handler rejects an edit that changes the host while keeping the stored key. It must fail closed: if it cannot read the account's current host, it rejects the edit rather than allowing it.
- https only. The key is a bearer token on every request, so the host validator rejects plain HTTP.
- Never log the key, and never put it in
inputs.conf. It belongs in Splunk's credential store.
The PostHog API
GET /api/organizations/<organization_id>/advanced_activity_logs/
?schema=ocsf&ordering=created_at&follow=true&page_size=200ordering=created_atreturns oldest first, so the saved cursor stays meaningful.follow=truekeeps thenextlink valid at the tail. Without it the link becomes null once you reach the newest entry and there is no position to resume from. With it, stop whenresultsis empty, not whennextis null.schema=ocsfreturns OCSF 1.5.0. Omit it and the response is PostHog's own shape.include_values=trueadds before and after values. Off by default because they can carry the content of the changed object.timeis epoch milliseconds per the OCSF spec, and Splunk wants seconds.metadata.uidis the activity log ID and is stable across replays, so it is the deduplication key.
Status codes worth handling distinctly: 401 bad or revoked key, 403 missing the
activity_log:read scope or not an org admin, 402 the organization's plan does not include
activity logs. The 402 comes from a Cloud-only entitlement check, so self-hosted never returns it.
Self-hosted PostHog behind a proxy must preserve the Host header. PostHog builds paging links
from the host it sees, so a rewritten Host produces links pointing at an internal address, which
the origin check then refuses to follow. This looks like collection stopping after one page.
Testing against a real Splunk
Run a Splunk container and install the built app. Two things about the common Splunk images:
docker execlands as a non-root user that cannot write into/opt/splunk. Use--user rootfor file operations. Searches work as the default user because they only talk to splunkd.- The bundled
splunkCLI may not reach splunkd, in which casesplunk install appfails. Untar the package straight into/opt/splunk/etc/apps/,chown -R splunk:splunkit, and restart.
To verify collection end to end, create an activity in PostHog and confirm it arrives:
index=<index> sourcetype="posthog:activity_log"
index=_internal source=*ta_posthog_activity_logs* # the add-on's own logA run that reports n_events=0 forever is usually the checkpoint, not a failure.
Splunkbase
Cloud approval is automatic when AppInspect's cloud tag returns zero errors, zero failures and
zero manual checks. Anything else means manual vetting or rejection. CI enforces that gate on
every build.
The warnings AppInspect still reports are all either informational ("no action required") or in vendored libraries, so they cannot be fixed from this repo.
AppInspect also reports future_failure checks, which pass today and are scheduled to become
failures. One is outstanding:
appserver/templates/base.htmlis a custom Mako template, deprecated in Splunk 10.4. UCC generates it and both views load the UI through it (template=indefault/data/ui/views/*.xml), so deleting it breaks the Configuration and Inputs pages. There is no UCC option to avoid it as of 6.5.3. This one has to be fixed upstream in UCC.
The python.required future failures were resolved by setting meta.supportedPythonVersion in
globalConfig.json, which makes UCC emit python.required into inputs.conf and restmap.conf.
Keep that list in step with the supported range above, and never hand-edit the generated conf.
Re-check the gate when bumping UCC. A future_failure becomes a hard rejection the day AppInspect
promotes it.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

