
process-watcher is an eBPF-based Linux monitoring agent that records every process a service starts in real time, giving defenders visibility into post-exploitation activity on systems they are authorized to protect.
| Tool | Neo23x0/process-watcher — a Linux eBPF process monitoring tool that watches a service and every process it creates, logging starts, program changes and exits as JSON |
| Category | eBPF-based host monitoring and detection tooling (Go, GPL-3.0) |
| Primary Use | Observing what a watched service like nginx, apache2 or php-fpm spawns, and streaming process.exec events with full lineage to a local JSONL file, TCP/UDP collector or webhook |
| Safe Use | Deploying on servers you own or administer during authorized security assessments, incident response and defensive baselining of production services |
| Telemetry Note | The agent itself is a defender-side collector: it attaches BPF programs in the initial PID namespace and requires root or CAP_BPF + CAP_PERFMON; its detections are JSON events in process-watcher.jsonl and optional remote outputs — it observes rather than being observed, and never polls /proc, so it leaves no gaps that short-lived malware could exploit |
There is a specific, painful gap in host detection: a web server or application server that suddenly executes sh, curl or wget is the textbook signature of a web shell or remote code execution, but those child processes are often so short-lived that polling-based monitoring misses them entirely. process-watcher, written in Go by the author behind the Neo23x0 account, closes that gap with eBPF. It follows a named service and every process it creates across all generations — even after the parent has exited — and logs each start, program change and exit as structured JSON, optionally with the full command line. The value proposition is stated plainly in the README: it makes the activity visible; it does not judge it. Baselines and verdicts are left to you or to whatever consumes the events.
The tool ships as a single static binary for Linux amd64 and arm64, with the eBPF programs embedded so nothing else is needed at runtime. Deployment is deliberately trivial: download the release archive, unpack, run. The author is explicit that 32-bit kernels are unsupported because the eBPF collector assumes 64-bit kernel pointers, and the requirements table pins the platform to Linux 5.15 or newer with BTF present at /sys/kernel/btf/vmlinux. Notably, the version number alone is treated as no guarantee — at startup the agent verifies the actual kernel functions and structure fields it needs rather than trusting the banner.
The invocation model is substring-based image matching. You tell it what to watch with --watch-image-contains nginx (or apache2, php-fpm, java), and it matches on the image — the file name of the program the kernel actually executed at exec — not on comm, which the README points out is changeable. This is an important correctness detail, and a good example of the author's adversarial thinking throughout: sh shows up as dash in the example output because the image is the program that really ran, not the name on the command line. Exclusions work the same way via --exclude-image-contains, and --capture-argv=true records full argument vectors, which the README warns may contain secrets and defaults to off for exactly that reason.
Architecturally, the collector refuses to degrade. If requirements are missing, the agent stops with a concrete diagnosis and exit code 3 rather than falling back to polling /proc, because polling would miss short-lived processes — the very thing the tool exists to catch. A doctor subcommand checks everything, loads the programs and runs a probe process of its own. Privileges can be either full root or a scoped capability set (CAP_BPF, CAP_PERFMON, with CAP_SYS_PTRACE, CAP_KILL and CAP_SYS_RESOURCE conditioning specific features), and kernel lockdown in confidentiality mode blocks operation entirely. The tool also refuses to run inside a container's PID namespace: it demands the initial PID namespace so all PIDs are host PIDs.
Output routing is where process-watcher fits into a larger defensive pipeline. Events land in a local JSONL file (./process-watcher.jsonl by default, with size-based rotation configurable via rotate_size_mb), and can be streamed simultaneously to a TCP collector, a UDP collector, or a webhook endpoint via --tcp-address, --udp-address and --webhook-url. The README ships minimal example receivers built only on the Python 3 standard library, and even suggests nc -lk 9000 for a first look — a refreshingly low-friction on-ramp for teams that want to evaluate before wiring a SIEM integration.
The event schema carries lineage. The quick-start jq example filters on .event_type=="process.exec" with .lineage.role=="descendant" and prints the root image, the child image and the joined argv, producing lines like nginx > dash sh -c id; uname -s. That root-to-child attribution is the analytical core of the tool: when a web server spawns a shell or a download utility, the event tells you what was started, by which process, and under which watched service — the context an analyst needs to separate a legitimate cron-like helper from an exploitation artifact.
Beyond observation, the tool has one optional reaction capability: kill rules. With --kill-enabled=true and matchers built from --kill-field (image or argv) plus --kill-string or --kill-regex, it can send a targeted SIGKILL to specific child processes — the README's overview diagram explicitly names reverse shells as the motivating case. The example regex ^(useradd|usermod)$ shows the intended shape: narrow, anchored patterns that kill account-creation tooling spawned by a web server. This is a defensive containment action on your own hosts, and the configuration defaults keep it disabled.
Configuration handling is stricter than most production software. Exactly one YAML file is loaded — files are never merged — with a search order of explicit --config, working directory, binary directory, then built-in defaults. Precedence is defaults, then YAML, then explicitly set flags. The loader rejects unknown or duplicate keys, wrong types, empty values, YAML aliases and multiple documents, requires config_version: 1, and errors name the file, line and key path (the README's example is kill.regex[2]). Environment variables are pointedly not a configuration layer; only the variable named by token_env is read, presumably for webhook bearer tokens. For a security tool, this paranoia is a feature: ambiguous configuration is a classic source of silently-wrong deployments.
The subcommand surface is small and well-documented. run is the default and waits even if no matching process is running yet; check-config validates without attaching BPF programs, creating files, contacting endpoints, or ever sending signals even with kill rules active; print-config dumps the resolved configuration with secrets redacted — only the variable name of the bearer token and the scheme and host of the webhook URL. Exit codes are documented (0 success including SIGINT/SIGTERM, 1 runtime error, 2 configuration error, 3 unsupported platform), diagnostics go to stderr, and stdout stays empty during run — small details that matter for scripting and supervision under systemd, for which a unit file and installer script ship in packaging/.
Supply-chain posture deserves mention: the README walks through verifying downloads against SHA256SUMS and checking build provenance with gh attestation verify, and the project publishes compatibility testing notes under docs/compatibility.md. At 23 stars the project is young, but the documentation density, strict config loader, and refusal-to-degrade design suggest an author who has operated detection infrastructure at scale and is building for reliability rather than novelty.
The README is currently topped by an operational alert about a critical PHP security release announced for 21 October 2026, covering two CVSS v4.0 9.2 vulnerabilities, and the release archive bundles ready-made PHP watch configurations under php/ (usable as process-watcher --config php/php-observe.yaml). The author is careful to state the tool's limits here: it does not look for these specific flaws, prevents nothing, and misses code that runs inside PHP without starting a process — it is a detection layer, not a patch substitute. That honesty about coverage boundaries is exactly the framing a professional should bring to any eBPF observer.
Where process-watcher fits in an authorized workflow is clear: deploy it on internet-facing servers you administer, before or during an engagement window, and let it build an evidence trail of every descendant process under your web and application servers. Pair the JSONL or webhook feed with a SIEM, baseline the legitimate helpers, and treat any sh, nc, python or download tool appearing under nginx or php-fpm as an immediate triage trigger. It is not an EDR replacement, it does not analyze in-memory execution, and it cannot tell you intent — but as a purpose-built lineage recorder for the moment a service starts spawning things it never should, it is a focused, well-engineered piece of defensive tooling.
Neo23x0/process-watcher.Educational analysis for authorized security professionals. Use only in controlled, authorized environments.
Related coverage
0 comentários:
Post a Comment
Note: Only a member of this blog may post a comment.