8.1 KiB
Go Port Handoff
Saved: 2026-07-22T02:48:00-07:00
Branch: main
Base commit: 6355fb4 (feat(go): expose retained incident views)
Status: implemented and green, but uncommitted
Worktree warning
The checkout is intentionally dirty and contains work from multiple related continuations. Do not reset, clean, or broadly restage it.
- The validation-sidecar, incident-persistence, bounded-enrichment, local-
assurance, asynchronous-enrichment, and incident-reader tranches are signed
commits
f85c2e8,538d1d9,fd2bbba,1ab4add,eff31b3, and6355fb4. The package-ownership enrichment slice is uncommitted. - At this checkpoint,
git status --shorthas 20 entries with untracked directories collapsed. - The Python GUI files and tests are separate pre-existing work. Preserve them while continuing the Go port.
build/is ignored and contains only local build output.
Implemented Go surface
The Go module requires Go 1.25 or newer and is currently verified with
go1.26.5-X:nodwarf5. It uses github.com/cilium/ebpf v0.22.0 and builds as a
static binary.
Polling and lifecycle
- All current Python poll detectors have Go parity:
reverse_shell,ld_preload,deleted_exe,input_snooper,credential_access,stealth_network,memory_obfuscation,egress,first_seen,new_listener,persistence, andnew_suid. --state-direnables isolated listener/SUID baselines, startup grace, persistence scans, and atomic first-seen retention.- The Go state directory must never be the Python daemon's
/var/log/enodia-sentineldirectory.
Event rules and native sources
- Exec rules: built-in SIDs
100001-100004plus configured rules. - Syscall rules: SIDs
100060-100066. - Typed host rules: SIDs
100067-100073and100076-100078. - Mixed JSONL routing, rule catalog operations, correlation SID
100080, and one shared cooldown gate are implemented. - Embedded CO-RE exec and filtered raw-syscall probes use
cilium/ebpfand fail open to polling on load/attach/read failures. - Native typed transports include UID/GID changes, chmod/chown variants,
capset,finit_module, confirmed interpreterwrite/writev/pwrite64/pwritev/pwritev2, and successful TCP connect/bind/listen/accept calls with/procendpoint resolution. - Generated little- and big-endian BPF objects live under
go-agent/internal/ebpfsource/. Runmake generate-goonly after changinggo-agent/internal/ebpfsource/bpf/*.bpf.c.
Production validation path
make build-gocreates a stripped, static, VCS-independent binary.make install-goinstalls the separate binary andenodia-sentinel-go-sidecar.servicewithout replacing or enabling the Python service.- The hardened systemd unit has a current
systemd-analyze securityexposure score of 4.3 (OK). It preserves the host/proc,/tmp, and network view because detectors require them, while hiding the Python state tree. - The unit uses
Type=notify; READY is sent only after baseline setup, event log preflight, and both requested probe-load attempts. - JSONL remains visible in journald and is also retained as private bounded
/var/lib/enodia-sentinel-go/events.jsonlplusevents.jsonl.1(64 MiB per active segment boundary). Non-status events are synchronized to disk. /var/lib/enodia-sentinel-go/heartbeatis atomically refreshed after each successful status emission.enodia-sentinel-go --healthreturnsenodia.go.health.v1JSON and a watchdog-friendly exit status.enodia-sentinel-go --events-tail Nreads active and rotated retained events chronologically, validates JSON, fails closed on corruption, and deliberately works without loading the agent configuration.- The unit now retains private
alert-YYYYMMDD-HHMMSS.{json,log}pairs using the requiredenodia.alert.snapshot.v1fields. Same-second alerts merge into one pair, process context is collected best-effort from/proc, and sharedmax_snapshots/max_snapshot_age_dayslimits are applied at startup, capture, and status time. enodia.status.v1totals, severity counts, and last-alert time are populated only from parseable retained Go snapshots. Python's unchangedlist_alertsandget_alertdata layer successfully consumed a Go-produced pair.- Snapshot incident IDs now reference an atomic private
incidents.jsonusing the requiredenodia.incident.v1fields. Grouping mirrors Python's process- lineage-first, time-window fallback behavior, the index is bounded to 1000 recent incidents, and same-second alert batches update one incident without duplicating the snapshot name. - Incident severity/signatures/SIDs/PIDs/lineage/timestamps are updated
additively and correlation SID
100080is persisted. Python's unchangedlist_incidentsandget_incidentconsumers successfully loaded a live Go-produced index, snapshot, and timeline. - A bounded no-I/O enrichment stage now derives Python-shaped process/parent context, lineage, remote-IP classification, and candidate paths entirely from already-captured records. Alert/process/indicator caps bound attacker- controlled work on the emission path.
- Snapshot log/JSON revisions now append synchronized private
enodia.hash_chain.v1records. Artifact SHA-256 is streamed, previous-hash lookup reads a bounded chain tail, and append completion is synced before the alert event is emitted. - A single 16-job asynchronous worker now streams SHA-256 only for regular executables at most 8 MiB and adds lstat file metadata. Slow I/O occurs outside the snapshot lock; a full queue simply leaves optional fields unknown.
- The asynchronous worker now resolves pacman/dpkg/rpm package-owner strings with a two-second bound and cache; ownership lookup never holds a detector lock or blocks alert capture.
- Richer integrity anchors and notification fan-out are not yet ported.
--incidents-listand--incident-show <id>now read the sidecar's isolated state without starting the agent. The latter returnsenodia.incident.view.v1with the incident, available snapshots, and time-ordered timeline.- No live system service was installed or enabled during development.
Last green verification
Run from the repository root:
python3 -m unittest discover -s tests -q
cd go-agent
GOCACHE=/tmp/enodia-go-cache go test ./...
GOCACHE=/tmp/enodia-go-cache go test -race ./...
GOCACHE=/tmp/enodia-go-cache go vet ./...
go mod verify
cd ..
make parity-go
make check-go-service
git diff --check
Results at save time:
- Python: 377 tests passed.
- Go unit and race suites: all packages passed.
go vetandgo mod verify: passed.- Parity: 22 poll alerts, 5 exec alerts, 7 syscall alerts, 10 typed-host alerts, 22 catalog records, 3 mixed-stream alerts, and 2 correlations.
systemd-analyze verifyand the three Go sidecar packaging/isolation tests: passed.- Static builds:
linux/amd64,linux/arm64,linux/riscv64, andlinux/s390xpassed. - Integration smoke tests passed for static binary identity, JSON emission, READY/STOPPING notification, heartbeat freshness/staleness, retained event equivalence, tail selection, corruption rejection, and evidence reading with malformed TOML. Snapshot smoke tests also passed for same-second batching, severity aggregation, private JSON/text pairs, truthful status counts, startup/quiet-period retention, process context, and Python dashboard-reader compatibility.
The Python suite prints three expected CLI error lines from negative tests and
one existing ResourceWarning for an implicitly cleaned-up HTTP 401 object;
the suite still exits successfully.
Resume here
- Add bounded integrity-anchor collectors to the existing asynchronous worker without destructive response behavior.
- Add a read-only management API consumer for isolated Go snapshot/event state while keeping Python authoritative.
- Continue broader Phase 3 rule metadata/state parity before considering any default-service or package cutover.
Keep the validation sidecar opt-in, preserve its separate state tree, and keep new attack/event primitives disconnected from destructive response paths.