Hostveil / Docs / CLI reference

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]
FlagDefaultDescription
--jsonfalseOutput the report as JSON.
--sariffalseOutput 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 FILEWrite 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 LISTScan 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 LISTScan every domain except these, comma-separated. Mutually exclusive with --only; the same partial-scan rules apply.
--glyphs NAMEremembered, else plainSymbol 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, -vfalseShow descriptions and fix guidance.
--no-colorfalseDisable 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.

Changed in 3.11

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:

HostveilSARIF levelGitHub security-severity
higherror9.5
mediumwarning5.5
lownote3.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]
FlagDefaultDescription
--allfalseApply every safe (Auto-fix) finding at once.
--reviewfalseWith --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-1For a Review fix, the 0-based alternative to apply.
--service NAME""Disambiguate a finding by service name.
--yesfalseApply 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]
FlagDefaultDescription
--forcefalseRestore 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 --scans

Lists checkpoints newest-first with a timestamp, the finding ID, a label, and either the exact hostveil rollback <id> command or not reversible.

FlagWhat it does
--scansShow 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.2

Leave 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.

FlagDefaultDescription
--trace FILE""Attach a command trace produced with HOSTVEIL_DEBUG=1.
--unredactedfalseSkip 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]
FlagDefaultDescription
--aifalseAppend 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]
FlagDefaultDescription
--format FMTRequired. One of json, sarif, markdown, docx, pdf.
--output FILEWrite to FILE instead of stdout. Required for docx and pdf — a terminal has no sensible way to show a binary format.
--only LISTScan only these domains, comma-separated. Domains not scanned show as N/A, never as 100.
--skip LISTScan 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]
FlagDefaultDescription
--aifalseAdd an AI verdict per finding. Ollama (local) by default; set HOSTVEIL_AI_PROVIDER for an external API.
--only LISTScan only these domains, comma-separated.
--skip LISTScan 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
FlagDefaultDescription
--clearfalseRemove 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]
FlagDefaultDescription
--addr ADDR127.0.0.1:8787Address to bind the dashboard to.
--theme NAMEremembered, else onedarkColor theme the dashboard opens in.
--layout NAMEremembered, else consoleScreen 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]
FlagDefaultDescription
--theme NAMEremembered, else onedarkColor theme: onedark, gruvbox, nord, catppuccin, tokyonight.
--layout NAMEremembered, else consoleScreen 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 NAMEremembered, else plainSymbol 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

CodeMeaning
0Success (and, for scan, no unfixed High findings).
1scan found unfixed High findings, or a command failed.
2Usage error — unknown command or missing required argument.
3scan 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 --check

Hostveil 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 byWhat update does
the install scriptDownloads the release archive and replaces the binary in place.
a .debDownloads the release's own .deb and installs it with apt-get, so dpkg keeps describing the filesystem correctly.
an .rpmThe same through dnf.
go installRebuilds 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.

FlagWhat it does
--checkReport 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.
--yesDo 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 uninstall

A 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.

FlagWhat it does
--yesDo not ask before removing.