CLI reference
Every subcommand, flag, and default. Commands that read root-owned files (SSH, firewall) or write to protected paths need root, so hostveil re-runs itself under sudo automatically (except version/help); set HOSTVEIL_NO_SUDO=1 to opt out.
Synopsis
hostveil <command> [flags]With no command on an interactive terminal, Hostveil opens the TUI; when stdin/stdout are not a terminal, it runs scan instead. hostveil version (or --version) prints the version; hostveil help (or --help) prints usage.
scan
Scan the host and report security findings.
hostveil scan [flags]| Flag | Default | Description |
|---|---|---|
--json | false | Output the report as JSON. |
--sarif | false | Output the report as SARIF 2.1.0, the format CI systems and GitHub code scanning ingest. Mutually exclusive with --json. Per-domain coverage rides in the run's properties, so a degraded scan is visible in the export too. |
--output FILE | — | Write the report to a file instead of stdout, in whichever format was chosen. The exit status is unchanged, so a CI gate keeps working. |
--only LIST | — | Scan only these domains, comma-separated (e.g. --only ssh,firewall). Domains not scanned show as N/A, never as 100. A partial scan is not saved as the last-scan baseline and reports no delta — otherwise the next full scan would announce every finding from the skipped domains as new. |
--skip LIST | — | Scan every domain except these, comma-separated. Mutually exclusive with --only; the same partial-scan rules apply. |
--glyphs NAME | remembered, else plain | Symbol set for status markers: plain or nerd. nerd draws them from a patched Nerd Font — install one and set HOSTVEIL_GLYPHS=nerd once. A terminal cannot be asked what font it has, so this is opt-in and the default changes nothing. |
--verbose, -v | false | Show descriptions and fix guidance. |
--no-color | false | Disable colored output. |
Exit code: 1 if any unfixed finding is High, 3 if a detection domain failed outright, otherwise 0 — see exit codes. Color is emitted only to a terminal and is suppressed when NO_COLOR is set.
What --json looks like
Every enumerated field is a lowercase name, not a number:
{
"findings": [
{
"id": "ssh.rootlogin",
"title": "SSH permits root login with a password",
"severity": "high",
"source": "ssh",
"remediation": "review"
}
],
"domains": [
{"source": "ssh", "state": "done", "finding_count": 5},
{"source": "cve", "state": "skipped", "reason": "trivy is not installed"}
]
}severity is one of high, medium, low. remediation is one of auto, review, manual, unavailable. source is the domain name, which is also the finding ID's prefix. state is one of done, degraded, skipped, error — and a domain that is not done is the difference between "nothing there" and "could not look", so read it before treating an empty result as a clean host.
A finding also carries fixed and, once something has been applied in the same run, pending. fixed means Hostveil applied a fix for it; pending means the host has not read that fix yet, so the finding is still charged in the score and still counts toward the exit status. A consumer deciding whether a risk is standing should read fixed && !pending rather than fixed alone. Both are omitted when false, and neither survives a fresh scan — a scan re-derives everything from the checkers.
These fields used to be integers, so every consumer had to keep its own copy of the ordering. Hostveil still reads the integer form, so a scan snapshot written by an older version is not lost, but it only writes names.
What SARIF export does with severity
SARIF has three levels where Hostveil has three, so the mapping is one to one:
| Hostveil | SARIF level | GitHub security-severity |
|---|---|---|
high | error | 9.5 |
medium | warning | 5.5 |
low | note | 3.0 |
The second number is the 0 to 10 scale GitHub code scanning sorts and filters by. Its buckets start at 9.0 for critical, 7.0 for high and 4.0 for medium, so a Hostveil High appears in GitHub's critical band, not its high one.
The mismatched words are deliberate. Hostveil's top level means the problem is reachable right now, from off the host, by someone holding nothing, and the band that means act today in code scanning is critical. Mapping the word to the word would have put those findings in a band people triage next sprint.
fix
Preview and apply the fix for a finding.
hostveil fix <finding-id> [flags]
hostveil fix --all [flags]| Flag | Default | Description |
|---|---|---|
--all | false | Apply every safe (Auto-fix) finding at once. |
--review | false | With --all, apply the Review fixes too, each through its first alternative. They are listed separately and counted separately: a Review fix is one Hostveil can perform and will not perform unattended, because it can cut off access to this host or runs a command with no checkpoint to undo. |
--action N | -1 | For a Review fix, the 0-based alternative to apply. |
--service NAME | "" | Disambiguate a finding by service name. |
--yes | false | Apply without the interactive confirmation. |
Applying always shows the diff/command, backs up the original to a checkpoint, then applies. See Fixing & rollback.
rollback
Undo a previously applied fix.
hostveil rollback <checkpoint-id> [--force]| Flag | Default | Description |
|---|---|---|
--force | false | Restore even if the file changed after the fix was applied. |
Takes one checkpoint ID (from hostveil history) and restores the backed-up file byte-for-byte. If the file no longer matches what the fix wrote, rollback declines and exits 1 rather than discarding your later edits — see when rollback declines.
history
List applied fixes and their rollback IDs.
hostveil history
hostveil history --scansLists checkpoints newest-first with a timestamp, the finding ID, a label, and either the exact hostveil rollback <id> command or not reversible.
| Flag | What it does |
|---|---|
--scans | Show the score of every retained scan instead of the applied-fix log, oldest first, with the change from the one before it. Hostveil keeps the last 30 scans. A scan where no domain could be examined shows N/A rather than a number, and no change is reported against it — a run nobody could score is not a drop to zero. |
Over SSH
The dashboard listens on the loopback interface, so the address it prints is that address on the server — typing it into the browser on your laptop reaches your laptop. When hostveil serve sees it is running in an SSH session it prints the forwarding command to run on your own machine, using the address your client actually reached:
ssh -N -L 8787:127.0.0.1:8787 you@10.0.0.2Leave that open, then open the printed URL on your own machine. Hostveil does not offer to bind the dashboard to a routable address instead: every route on it applies fixes or reads a scan of /etc/shadow, as root, and a forwarded port is the same access with the authentication already done.
diagnostics
Collect the pieces a bug report needs into one file. It never touches the network — there is no send step, only local collection you attach to an issue by hand.
hostveil diagnostics [flags]The report carries hostveil's version, the host's distro and platform, any recorded crash traces, and the last saved scan's score, domain states, and finding IDs and severities — never a finding's description, remediation text, or evidence, which can quote the very configuration an operator is asking about.
| Flag | Default | Description |
|---|---|---|
--trace FILE | "" | Attach a command trace produced with HOSTVEIL_DEBUG=1. |
--unredacted | false | Skip redacting IP addresses and home-directory usernames. Local use only. |
--output FILE | "" | Write the report to a file instead of printing it. |
Printing the report also names where to paste it: https://github.com/seolcu/hostveil/issues/new. Nothing here reaches the network on its own — see Environment variables for what does.
explain
Explain a finding in plain language.
hostveil explain <finding-id> [flags]| Flag | Default | Description |
|---|---|---|
--ai | false | Append an advisory AI explanation. Ollama (local) by default; set HOSTVEIL_AI_PROVIDER for an external API. |
--service NAME | "" | Disambiguate a finding by service name. |
The built-in plain explanation always prints; --ai adds an advisory section. See AI explanations.
export
Scan the host and export the report in one of five formats. json and sarif are byte-identical to scan --json/--sarif, for another program to read. markdown, docx, and pdf explain each finding and its fix in plain language, for a person reading the result or handing it to a colleague.
hostveil export --format FMT [flags]| Flag | Default | Description |
|---|---|---|
--format FMT | — | Required. One of json, sarif, markdown, docx, pdf. |
--output FILE | — | Write to FILE instead of stdout. Required for docx and pdf — a terminal has no sensible way to show a binary format. |
--only LIST | — | Scan only these domains, comma-separated. Domains not scanned show as N/A, never as 100. |
--skip LIST | — | Scan every domain except these, comma-separated. Mutually exclusive with --only. |
Exit code is the same gate as scan — see exit codes.
advise
Scan the host and list every finding with a registered fix, alongside what applying it would get you and cost you. With --ai, add a verdict — Apply, Skip, or Depends — weighed against the host description saved with ai-context, if one is set. See AI explanations.
hostveil advise [--ai] [flags]| Flag | Default | Description |
|---|---|---|
--ai | false | Add an AI verdict per finding. Ollama (local) by default; set HOSTVEIL_AI_PROVIDER for an external API. |
--only LIST | — | Scan only these domains, comma-separated. |
--skip LIST | — | Scan every domain except these, comma-separated. Mutually exclusive with --only. |
The deterministic listing always prints, with no AI required. --ai adds the situational verdict beneath it, or says why it could not.
ai-context
Show, set, or clear the saved one-line description of this host — "a personal media server, want fast security patches more than stability" — that explain --ai and advise --ai use to judge a fix for this host specifically rather than in the abstract. Remembered in the same state directory as everything else hostveil keeps, until changed or cleared.
hostveil ai-context # show the current description
hostveil ai-context "TEXT" # set it
hostveil ai-context --clear # remove it| Flag | Default | Description |
|---|---|---|
--clear | false | Remove the saved description instead of showing it. |
serve
Run the localhost web dashboard. hostveil web is an alias.
hostveil serve [--addr ADDR] [--theme NAME] [--layout NAME]| Flag | Default | Description |
|---|---|---|
--addr ADDR | 127.0.0.1:8787 | Address to bind the dashboard to. |
--theme NAME | remembered, else onedark | Color theme the dashboard opens in. |
--layout NAME | remembered, else console | Screen arrangement the dashboard opens in. A choice made in the browser’s own picker is stored there and wins for that browser, so this sets what a browser that has never chosen gets. |
The command prints a URL containing a one-off access token; open that URL rather than typing the bare address. Every route requires the token, because loopback does not separate the operator from other local accounts and the dashboard runs as root.
The dashboard only answers requests addressed to localhost, so --addr cannot publish it to the network — a non-loopback address binds, then refuses anything that reaches this machine by IP or hostname, and Hostveil says so. A forwarded port keeps working, because the browser says localhost either way. Use ssh -L 8787:127.0.0.1:8787 you@server to reach it remotely.
tui
Open the interactive terminal UI (also the default with no command on a terminal).
hostveil tui [--theme NAME] [--layout NAME] [--glyphs NAME]| Flag | Default | Description |
|---|---|---|
--theme NAME | remembered, else onedark | Color theme: onedark, gruvbox, nord, catppuccin, tokyonight. |
--layout NAME | remembered, else console | Screen arrangement: console, split, triage, railverdict, lanes, inline. Press l inside the TUI to change it and have the choice remembered. Precedence is this flag > HOSTVEIL_LAYOUT > the remembered choice > console. |
--glyphs NAME | remembered, else plain | Symbol set for status markers: plain or nerd. nerd draws them from a patched Nerd Font — install one and set HOSTVEIL_GLYPHS=nerd once. A terminal cannot be asked what font it has, so this is opt-in and the default changes nothing. |
Requires an interactive terminal; otherwise it tells you to use hostveil scan. Press t inside the TUI to pick a theme and have it remembered — see Interfaces.
Exit codes
| Code | Meaning |
|---|---|
0 | Success (and, for scan, no unfixed High findings). |
1 | scan found unfixed High findings, or a command failed. |
2 | Usage error — unknown command or missing required argument. |
3 | scan had a detection domain fail outright, so it covered less of the host than it should have. A domain skipped for a missing dependency, or degraded to partial coverage, does not produce this — both are reported in the output instead. |
update
Update Hostveil to the latest published release.
hostveil update
hostveil update --checkHostveil works out how this binary was installed and updates it the same way, so nothing ends up disagreeing about what is on the host:
| Installed by | What update does |
|---|---|
| the install script | Downloads the release archive and replaces the binary in place. |
a .deb | Downloads the release's own .deb and installs it with apt-get, so dpkg keeps describing the filesystem correctly. |
an .rpm | The same through dnf. |
go install | Rebuilds from source with the Go toolchain. Replacing a binary you built with a published one would discard your build without saying so. |
Verification is not optional. The download is checked against the release's own checksums file and discarded if it does not match, and where the GitHub CLI is present the signed build provenance is verified too. A provenance check that fails stops the update; one that cannot run because gh is not installed is a note, because a checksum proves the bytes arrived intact and nothing about who produced them.
If Hostveil cannot tell how the binary was installed, it does nothing and says so. Guessing there means overwriting a file some other tool believes it owns.
| Flag | What it does |
|---|---|
--check | Report whether a newer version exists and exit without installing. Exit status 10 means an update is available, 0 means this is the latest, so a cron job can branch on it without reading the sentence. |
--yes | Do not ask before installing. |
The daily check
A scan, a dashboard and the TUI each ask GitHub once a day whether a newer release exists, and print one line about it if there is. The request runs in the background, so nothing waits for it, and a run that fails is silent: a host behind a proxy is not a host with a problem Hostveil should be reporting.
The line never appears in --json, in SARIF, or in a report written to a file with --output. Those are read by machines and read later.
Hostveil never installs anything on its own. Set HOSTVEIL_NO_UPDATE_CHECK=1 and it stops contacting GitHub entirely, except when you type hostveil update.
uninstall
Remove Hostveil, through whichever tool installed it.
hostveil uninstallA packaged install is removed with apt-get remove or dnf remove; anything else deletes the binary.
Your saved scans and rollback checkpoints are left alone, and Hostveil prints where they are before it asks. Those checkpoints are the backups of every file it edited on this host, so removing the program is not a reason to throw away the ability to undo its work. Delete them yourself when you are sure.
| Flag | What it does |
|---|---|
--yes | Do not ask before removing. |