Inspiration

Supply-chain attacks keep landing as "a Node process read your secrets." That's useless when the process has 800 dependencies. After Shai-Hulud / event-stream style incidents, I wanted the kernel answer with a package name on it — not another SCA scanner that only looks at lockfiles.

I build Goodman at Heisenbug. The bet is simple: if you can attribute the syscall to package@version, you can learn a baseline and notice when that package starts doing something new.

What it does

Goodman watches Node/Python processes with CO-RE eBPF (open/openat/connect/execve + optional LSM block, off by default). It captures the user stack, resolves it through V8/CPython perf maps and /proc/<pid>/maps, and maps the deepest node_modules/ or site-packages/ frame to a real version from package.json / dist-info.

Then it:

  • fingerprints behavior per (service, package, version) — e.g. READ …/.aws/credentials, CONNECT 169.254.169.254:80
  • diffs live behavior against the learned baseline
  • fires always-on high-risk rules even during the learning window (no "poison the baseline" gap)
  • serves alerts over REST + SSE, Prometheus metrics, and an embedded dashboard

Wrong attribution is worse than unknown — we return <unknown> rather than guess.

OpenClaw angle (demo + next): OpenClaw is a Node process; ClawHub skills are npm packages. Tier-1 npm attribution via V8 perf maps is exactly the stack for that. Demo will show which skill made a given syscall — not "something read a cred file," but skill-xyz@1.2.3 did. Goal: one-command integrate Goodman with OpenClaw, plus the same /v1/* API for SIEMs / OpenClaw / anything else without needing our UI.

How we built it

Kernel side is CO-RE C (bpf/goodman.bpf.c) with a strict byte-layout contract against Go RawEvent — silent struct drift = garbage attribution, so we lock that with offset tests.

User space is Go:

  • internal/loader — load/attach/ringbuf (never block the reader)
  • internal/attribute — maps + perf-map resolve → package@version + canonical behavior
  • internal/fingerprint + internal/diff — baselines and config-driven high-risk rules
  • internal/api — collector, SSE, embedded React dashboard
  • Helm/Docker for K8s; SQLite + Postgres on one store path

I used Codex (GPT-5.6) throughout the build week — not just scaffolding. Concrete places it helped: keeping C/Go event layout in sync, attribution edge cases (container /proc/<pid>/root perf maps), rule/diff behavior, Helm/auth surfaces, dashboard wiring to live /v1/*, and tightening README + demo scripts so judges can run make demo without root. I still reviewed every change; the hard invariant (never misattribute) stayed mine.

Verification without root: make test, make smoke, make replay (real npm attack behaviors). Live eBPF path: sudo make e2e on a real kernel.

Challenges we ran into

  • Attribution is the product. V8 JIT frames, container mount namespaces, relative paths + dirfd, and "never guess a package name" fought us more than the eBPF boilerplate.
  • Ringbuf must stay lossy-fast. Anything that blocks the sensor hot path drops events — batching/spool to the collector had to stay boring.
  • LSM enforcement is easy to get wrong. Fail-open, off by default, literal deny keys only, and LSM attach failures must not tear down detection.
  • Demo vs reality. Judges often can't load BPF in a sandbox, so we invested in make demo / smoke / replay so the full collector→alert path is provable without root.

Accomplishments that we're proud of

  • End-to-end path that actually attributes to package@version, not just "node did a thing"
  • Always-on high-risk rules during learning (closes the baseline-poisoning window)
  • Replay corpus for real incidents (event-stream, eslint-scope, ua-parser-js, node-ipc, etc.)
  • Production-shaped deploy: Helm, auth classes, SSE dashboard, optional block mode
  • API that any SIEM / agent runtime can consume without our UI

What we learned

Kernel visibility without package identity is half a product. Stack → path → package.json sounds small; it's most of the work. Also: Codex is strongest when you keep it on the invariant loop (layout tests, attribution fixtures, smoke) instead of "generate me an app."

What's next for Goodman

  • OpenClaw / ClawHub demo: watch the OpenClaw Node process, attribute syscalls to specific skills, show that in the walkthrough
  • One-command OpenClaw integrate (install/config so Goodman attaches to the OpenClaw runtime with minimal friction)
  • SIEM-first export: keep leaning on /v1/alerts, /v1/stream, fingerprints export — same data, any consumer
  • Broader skill/runtime coverage and tighter pilot packaging for teams running agent stacks in K8s

Built With

Share this project:

Updates