Hostveil / Docs / Contributing

Contributing

Hostveil is Go, with a small dependency tree and a strict architecture. This page is a quick orientation — the canonical developer guide is docs/DEVELOPMENT.md, and the demo VM is documented in demo/README.md.

Start here

git clone https://github.com/seolcu/hostveil.git
cd hostveil
go build ./cmd/hostveil     # produces ./hostveil
go test ./...

Before opening a PR, run the full suite CI runs — go build ./..., go vet, gofmt -l ., go mod tidy, and go test -race ./.... The exact commands and the pinned Go version are in DEVELOPMENT.md.

Linux tool, portable tests

Hostveil builds and its tests pass on any OS, but running the binary meaningfully needs Linux with the relevant tools present — use the demo VM rather than your own machine.

How it's built

The engine lives in internal/core; the CLI, TUI, and web UIs are thin layers over it. An import-lint test enforces that ui/* never imports fix, history, check, or compose — that is what keeps the three interfaces behaving identically. The full repository layout and architecture notes are in DEVELOPMENT.md.

Extending it

There is no plugin system, no rule file and no config file, and that is a decision rather than a gap. The axis weights have to sum to 100 for the score to mean anything; every finding has to carry a remediation someone argued about, and the register of findings deliberately left unfixed is prose in a doc comment that tests parse; and the distribution story is one signed static binary. A rule format would put all three behind a file nobody signs.

So extending Hostveil means writing Go and rebuilding — and the three things you might want to add differ in size by an order of magnitude.

What you are addingWhat it takes
A rule in an existing domainA function returning a finding, a row in its domain’s rule table, a test built through internal/check/checktest, and a row in the checks table in both languages. Small.
A fix for an existing findingA builder, a registration, and a decision: if it is not registered it must be named in the register in fix.Default()’s doc comment with the reason, and given a one-sentence decline reason a user will see. Tests hold all three together. Small.
A whole detection domainA real change. Below.

A new domain is one package implementing Checker — and then:

  • a row in sourceDefs, which carries the domain’s name, label, axis and weight;
  • a rebalance: the weights sum to exactly 100 and a test enforces it, so a new domain is always funded by taking points from existing ones, and each transfer wants an argument;
  • registration in cmd/hostveil/app.go, in the order the domain table declares;
  • rows in the checks table in both languages, with a severity and a fix kind for every finding, plus the weight column;
  • the weight in the scoring page’s table, in both languages;
  • a decline reason for every finding you do not register a fix for;
  • and, if the domain would answer wrongly rather than skip on a non-Linux host, an entry in the OS-gate list with the reason.

Around eleven tests fail until each of those is done, most of them in the documentation guards rather than in the code. That is the intended experience: the tests are the checklist, and they are the reason the docs describe the tool that exists.

Running it for real: the demo VM

demo/ is a code-defined, deliberately vulnerable Ubuntu server; Hostveil is built from your working tree inside the VM, so it always reflects your current code. Bring it up with ./run.sh up and scan with ./run.sh scan. Per-platform provider setup, the 5-minute demo script, the reset workflow, and troubleshooting all live in demo/README.md.