colonizer
install
docs Security hunters

#Security-hunter modules

What ships today: one Manifest per hunter, a hunters.lock pin per platform, an operator opt-in (COLONIZER_HUNTER_INSTALL=1) guarding a Linux-only, race-free, checksum-verified on-demand install, a capability probe, and a manifest-driven scan runner (hunters::scan) that renders the scan template, runs the installed binary, and parses the artifacts back into Findings. A red-team run now drives Shannon, one colony per repository (see below); Strix is installed and probed but no run drives its scans yet, and colonies have no Docker daemon. In the cockpit's red-team wizard the swarm and Shannon are selectable cards — Shannon reads "Ready · runs in a colony" — and Strix stays a disabled card marked "Coming soon"; the red-team API accepts "hunter": "shannon" and refuses Strix with a 400 (see red-team.md). The colony swarm with the Security preset is still the way to a security-focused raid that needs no external hunter: security focus areas, a deterministic pre-scan and an operator checklist (red-team.md).

#On demand, verified, never vendored

Hunter binaries download on demand at a pinned version and are checksum-verified before they run. They are never vendored into the binary or the repo, which keeps licences clear: Strix is Apache-2.0 and Shannon is AGPL-3.0, and the mothership shells out to both rather than linking anything. Shannon is not even a download — it installs via npx as a Node package.

#The manifest

Each hunter is a Manifest in crates/colonizer/src/hunters.rs (builtin()), with one row per field:

FieldWhat it is
idShort stable id, used in routes, lock lookups and the on-disk cache
nameDisplay name
descriptionOne line on what the hunter does
homepageProject homepage
logoName of the intended inline-SVG icon, or the short name as a text fallback
licenceSPDX licence id
pinned_versionHunter version this build drives
runtimebinary (downloaded) or node (via npx)
needs_dockerWhether a scan needs a Docker daemon
installThe only supported install path: the POST /api/hunters/{id}/install route, which installs the pinned, checksum-verified artifact from hunters.lock behind the COLONIZER_HUNTER_INSTALL opt-in
scanHeadless scan invocation template with {target}/{mode}/{out} placeholders; scan substitutes them as whole argv entries and runs the installed binary
outputRun-relative filenames the hunter writes; the first entry is the findings artifact scan parses
findings_formatWhich parser reads the output: strix_json or sarif
gateway_envEnv var scan sets to the gateway's base URL for a run (the token goes to LLM_API_KEY); the gateway is required for scanning
availableTrue once a parser plus a checksum pin ship; false is a manifest-only stub

#The two runtimes

binary hunters (Strix) download from hunters.lock into <data_dir>/hunters/<id>/<version>/ and run from there. node hunters (Shannon) run through npx with the pinned package version, so there is nothing to download or verify — the install command is the whole story.

#Docker stays out of reach

Colonies are KVM microVMs without a Docker daemon, so a hunter with needs_docker (Strix) is reported not ready and stays not runnable until Docker runs inside the colony microVM. The host's daemon is never shared with a colony and never suggested: Docker socket access is host root, and a hunter like Strix runs arbitrary proof-of-concept code, so pointing a colony — or anything a hunter runs — at the host daemon would hand it the host. The capability probe (GET /api/hunters/{id}/probe) reports the runtime plus Docker separately; a missing daemon says plainly that the colony has none yet and the host's is never shared. A future run stage that spawns Strix must target a Docker daemon inside the colony microVM.

#On-demand install

Binary artifacts pin in crates/colonizer/hunters.lock (id, version, platform, kind, sha256, url — the same columns as headroom.lock), compiled into the binary and cached under <data_dir>/hunters/<id>/<version>/. Installing is opt-in: POST /api/hunters/{id}/install returns 403 unless COLONIZER_HUNTER_INSTALL holds a truthy value (1/true/on/yes, case-insensitive). Pins are Linux-only, so macOS and Windows report the hunter as not installable and not installed. Each hunter has its own async mutex held for the whole install, so concurrent installs of the same hunter serialise and the second returns after re-checking instead of downloading again. The download is bounded (256 MiB, roughly 3x the ~89 MB Strix 1.6.2 tarballs), refuses an oversized Content-Length before reading the body, follows redirects only over https (at most ten hops), and times out after ten minutes. The sha256 is verified over the downloaded bytes, which are piped straight into tar over stdin — no tarball file ever lands on disk — and the binary lands atomically (a uniquely-named temp file in the version dir, chmodded to 0o555, then renamed over the final path), so a failed download never leaves a half-written binary behind and readers never see one mid-write.

#Running a scan

hunters::scan(manifest, request) is the manifest-driven scan runner. It refuses a hunter that is a manifest-only stub, checks the binary is installed and the target is a directory, renders the scan template into argv (placeholders become whole argv entries — no shell), and runs the installed binary with the working directory set to the request's work_dir, where it then looks for the run's artifacts — at most three levels deep, never following symlinks. Strix writes strix_runs/<run-name>/vulnerabilities.json, which that finds without knowing either name.

#Planned, not implemented (tracked in #933)

#216 closed with these left, and so did its breakdown #336 and its follow-ups #442; none of them built the work, so it is still open under #933.

#How to add a hunter

  1. Write a Manifest and add it to builtin().
  2. A SARIF-emitting hunter needs no new parser — reuse parse_sarif.
  3. A non-SARIF hunter adds one parser function and a FindingsFormat variant, then dispatches it from normalize.
  4. Pin binary artifacts in hunters.lock for both architectures.
  5. Flip available once the parser and the pin are in.

#Current state

Strix is phase one: manifest, pinned lock, opt-in install, probe, two parsers (parse_strix, parse_sarif), and a scan runner that wires them end to end — template to argv, artifacts to Findings, cost into the outcome. No run drives a host-side scan yet, and colonies have no Docker daemon, so hunters::scan runs nothing today. Shannon is phase two: its manifest stays available: false, so host-side install and scan still refuse it as a manifest-only stub, but a hunter: "shannon" red-team run drives it inside a colony — the host writes the pinned, npx-based command into the colony's brief, the colony starts the app and runs Shannon against it in its microVM, and the host reads the SARIF it leaves behind back through orchestrator validation (see red-team.md). Because Shannon is AGPL, the mothership never fetches or links it: only the in-colony command line is written. The cockpit module gallery will extend the existing /api/plugins surface rather than adding a parallel one, and the logo field is a name only until that gallery renders it.

In the cockpit, the red-team wizard calls the probe for both hunters and shows the swarm and Shannon as selectable cards with bundled logo images, Strix disabled; the manifest's logo field is not used there. There is no hunter gallery and no install button in the cockpit: installing Strix is the POST /api/hunters/strix/install route above, with the opt-in set.