Documentation

picket is a host threat-hunter. It pushes a read-only sweep to each server in your inventory over SSH, scores what it finds, and — only when you arm it — freezes the unambiguous malware. This page covers the host surface; the identity engine (picketd) lives in the repo.

install inventory run arming what it checks containment scheduling llm triage identity surface

install

curl -fsSL picket.sh | sh

macOS (Apple silicon) and Linux (x86_64) get a prebuilt static binary. Any other target builds from source, which needs Rust. The script drops a single picket binary into /usr/local/bin (or ~/.local/bin if that isn't writable) and touches nothing else. The binary the controller runs is the only thing installed; the sweep itself is shipped to each host over SSH at runtime, so there is nothing to install on the boxes you watch.

inventory

picket reads inventory.toml from the working directory (or --inventory PATH). Each host is an SSH alias from your ~/.ssh/config — the controller shells out to the system ssh, so it inherits your keys, ProxyJump, and ControlMaster.

# inventory.toml
[settings]
armed = false            # observe-only until you trust the calls
ssh_timeout_secs = 45

[[host]]
ssh = "build-host"
note = "coolify — runs untrusted app containers"

[[host]]
ssh = "hetzner"
note = "primary web + app services"

run

commandwhat it does
picket runobserve-only sweep of every host in the inventory
picket run --host NAMEsweep a single host
picket run --armedallow auto-containment of slam-dunks for this run
picket run --jsonprint the raw fleet report as JSON

The exit code is 1 if anything alertable was found and 0 if the fleet is clean, so a timer or CI wrapper can page on it.

$ picket run
=== build-host ===   load 0.11   ✓ clean
=== hetzner ===      load 2.54   ✓ clean
=== krawler ===      load 0.02   ✓ clean

arming & the disposition rule

Every finding carries a severity and a slam_dunk flag, both decided on the host where the live process context exists. The controller only ever narrows from there — it never upgrades a finding, and never auto-acts on anything ambiguous. That rule is unit-tested.

findingarmedaction
slam-dunkyessnapshot evidence, freeze, alert
slam-dunknoalert only ("would contain")
high / critical, not slam-dunkanyalert only
info / low / mediumanyrecorded, not surfaced

Start with armed = false. Watch a few runs, confirm the slam-dunk calls look right, then flip it.

what it checks

Two thresholds are env-tunable: PICKET_CPU_HOT (default 50) and PICKET_BAD_PORTS.

containment

Containment runs only for slam-dunks, only when armed, and is reversible. It snapshots before it touches anything — recovering the deleted binary from /proc/<pid>/exe, capturing the environment, open sockets, and (for a container) docker inspect/top — into /root/incident-<ts>/. Then it freezes:

A false call costs you a frozen process and an evidence directory, never a service you can't explain.

scheduling

The repo ships systemd/picket.service + picket.timer — a oneshot that wakes every 15 minutes on a controller host that has SSH to the fleet.

cp picket /usr/local/bin/
mkdir -p /etc/picket && cp inventory.toml /etc/picket/
cp systemd/picket.{service,timer} /etc/systemd/system/
systemctl enable --now picket.timer

llm triage

Set PICKET_TRIAGE_CMD and, when a sweep surfaces anything alertable, picket pipes the full fleet JSON to that command's stdin and prints its assessment. The model sees only collected JSON — never a shell, never the hosts. This is the "model only on hits" path: the deterministic sweep is free every cycle, the model is paid for only when something is found.

PICKET_TRIAGE_CMD='claude -p "Triage these host-security
  findings — real compromise vs benign ops noise?"' \
  picket run

the identity surface

picket's other half is picketd: a behavioral risk engine that step-ups suspicious signins. It's a better-auth plugin running a three-stage pipeline — a deterministic scorer, an online Gaussian, and a prompt-injection-hardened LLM judge — with AEAD-sealed signatures and a signed-WASM plugin sandbox. It shares picket's spine: deterministic on the hot path, an LLM only for the ambiguous case, nothing uncertain auto-acted on. See the repo for setup.