Handbook sections▾
Foundations
Understanding the threats to a Linux hostWhy we hardenThe defense principles behind every ruleEffective configuration: the truth no file holdsThe standards Pavois maps toSOCLE control ID modelEvidence & exportsHow the A-E grade is computedWhat a PASS proves: the qualified verdictTrust model for the evidence bundleIs SOCLE just your own norm? (governance & the circularity question)What Pavois covers, and what it doesn'tDomains
Hardening SSHHardening PAMMandatory Access Control (SELinux / AppArmor)Hardening the host firewallHardening sudoFile Permissions & OwnershipMount & filesystem hardeningHardening kernel modulesHardening the kernel & network with sysctlAudit logging with auditdHardening logging with journald and rsyslogHardening systemd servicesPackage hygieneHardening the bootloader (GRUB)Synchronizing timeLogin banners and MOTDControlling cron and at accessHardening the GNOME desktop (dconf)Tooling
Undo a hardening run: restore pointsOperating Pavois: privileges, air-gap, timing, exceptionsRun Pavois in CI (GitHub Actions)Feature status: delivered, partial, roadmapGovernance: licence, versioning, provenance, securityOperating Pavois: privileges, air-gap, timing, exceptions
Last reviewed
What Pavois needs from the target (root, and why a narrow sudoers does not work), how it runs without internet access, how long a scan actually takes, and how to record an accepted risk an auditor will accept.
Four questions decide whether Pavois fits your environment, and none of them has a comfortable answer that is also true. Here are the true ones.
First, what Pavois is for
Pavois builds a compliant image. It does not rescue a legacy host that was misconfigured for ten years.
That is not a limitation to apologise for, it is the shape of the problem. A separate /var/log filesystem is decided when the disk is partitioned. A kernel without the KSPP options cannot grow them at runtime. Those controls carry the classes install-time and kernel-build precisely because no apply can close them on a running production box: you do not repartition a machine that is serving traffic.
So the workflow that works is:
- Build: a fresh cloud image, the partition recipe, the kernel recipe,
harden applyconverged. That is your golden image, and its grade is the grade your fleet starts from. - Ship: bake it (Packer, an image pipeline, a template).
- Watch: scan the running fleet to detect drift from the image you shipped, and
harden applythe drift back.
Run it against a legacy host and it will still tell you the truth, in detail, with a grade. It will simply also tell you that about one gap in five needs a rebuild, not a command (53 of the 281 failing controls on a fresh debian 12: the install-time and kernel-build classes). That is the honest answer, and it is the same one for every tool: a partition table is not a setting.
Privileges: root, and why a narrow sudoers does not work
Pavois audits the effective configuration, and the commands that produce it are privileged: sshd -T (root only), auditctl -l, sysctl -a, reading /etc/shadow. There is no read-only subset that yields the same answers.
Two transports, two very different sudo footprints:
| What runs on the target | What sudoers must allow | |
|---|---|---|
| SSH (default) | every probe command, one at a time, each prefixed with sudo |
effectively any command: the control corpus shells out to dozens of different readers |
--on-target |
one command: sudo cinc-auditor |
a single binary |
--on-target is therefore the one to reach for if you want a scoped sudoers rule: the account may be restricted to cinc-auditor alone. Be clear-eyed about what that buys you, though: cinc-auditor then runs as root and can do anything root can. It is a smaller attack surface for a stolen credential, not a privilege boundary.
The sudo password never reaches argv: it is passed to the engine on stdin (--config -), so it cannot be read from ps or /proc/<pid>/cmdline. Use --sudo-prompt (no echo) or the PAVOIS_SUDO_PASSWORD environment variable. Passing --ssh-pass on the command line is possible and discouraged, for exactly the reason you would expect.
Which sudo flag, and what a cloud image gives you
Three ways to reach root, and the right one depends on the target's sudoers, not on preference:
| Flag | Use it when | What Pavois does |
|---|---|---|
--sudo |
the account is NOPASSWD (every cloud image ships this way), or you already export PAVOIS_SUDO_PASSWORD |
runs sudo with no password exchange |
--sudo-prompt |
sudo asks for a password and you want to type it once, without echo | reads it from the terminal, or from PAVOIS_SUDO_PASSWORD when there is no terminal (CI) |
| neither | the account you connect with is already root | nothing |
Check before you choose, it takes one command:
ssh user@host 'sudo -n true && echo NOPASSWD || echo "sudo wants a password"'If it prints NOPASSWD, use --sudo. Cloud images almost always do: AWS, Outscale, Scaleway,
Azure and the Debian/Ubuntu/RHEL official images all provision their default account with
NOPASSWD:ALL.
Do not answer an empty password. --sudo-prompt on a NOPASSWD host asks for something that
does not exist, and pressing Enter hands Pavois an empty password, which is not the same thing as
"no password needed". Up to v0.3 that combination made harden apply stop dead at photographing
the prior state (restore point), with no error and no timeout. It is fixed, but the flag is still
the wrong one: use --sudo.
Scanning without root does not give you a smaller answer, it gives you a wrong one. The
privileged probes return nothing at all unprivileged (sshd is not even on a normal PATH), and
every check reading them reports a deviation nobody measured. On a host we had just hardened,
dropping --sudo turned grade C with 0 critical findings into grade E with 2 fabricated
critical findings. pavois scan now refuses to run unprivileged rather than publish that;
--allow-unprivileged overrides the refusal if you understand what the report then means.
Air-gap: what actually needs the network
Three things reach out, and each has an offline answer:
| What | Why | Offline answer |
|---|---|---|
| CINC Auditor | never installed on its own. On the scanning host you install it (pavois doctor prints the command). On a target, --on-target stops when the engine is missing and installs it via omnitruck only if you pass --bootstrap-cinc |
pre-install the CINC package from your internal mirror and never pass the flag; Pavois then never calls out |
harden apply |
needs cinc-client (the converge engine) on the target. When it is absent the apply stops and tells you what it would have run; it installs it via omnitruck only if you pass --bootstrap-cinc |
same: pre-install it from your internal mirror and never pass the flag |
| package remediations | apt-get / dnf install what a control needs |
point the target at your internal package mirror |
The rule corpus is local (it ships with the binary), the OSCAL bundle is generated locally, and a scan sends nothing anywhere. An air-gapped scan is a solved problem: bake CINC into the image, and Pavois is offline by construction.
How long a scan takes
Measured on 14 July 2026, on a debian 12 VM (2 vCPU, 3 GB, same LAN), 620 controls, wall-clock of the full pavois scan in each mode:
| Mode | Duration | Why |
|---|---|---|
--on-target |
13 seconds | the engine runs ON the target: one SSH session, no round-trip per command |
| SSH (default) | 90 seconds | every probe is an SSH round-trip; latency multiplies by the number of commands |
Seven times faster, and the gap widens with latency: over a WAN link, the SSH mode is dominated by round-trips, not by the checks. --on-target copies the profile to the target and runs the engine there: which is why it needs the engine there.
A fleet loop is therefore cheap: 200 hosts × 13 seconds, parallelised, is minutes, not an evening.
Exceptions: two different things, and only one is a waiver
A waiver is a control Pavois deliberately does not enforce, because enforcing it would break something the project ranks higher. It lives in the rule base (waiver: in rules.yml), renders into the profile's waivers.yml, and the scan reports the control as waived rather than failed. The justification is what an auditor reads:
# profiles/linux/<os>/waivers.yml (generated: the source is rules.yml)
kmod-loading-disabled:
justification: 'kernel.modules_disabled=1 permanently blocks module loading, which strands the
nftables firewall (it needs nf_conntrack for `ct state`) and can cut SSH. Enforcing it would
break a control we rank higher (a working host firewall). Accepted risk: modules stay loadable.'
run: falseThat is Pavois's own accepted risk, shipped with the tool, identical for every user. It is not your exception.
Your exception is a business decision, and it belongs in the evidence, not in the rule base: this control, on this host, is not applied, because of that, until then, approved by them. pavois bundle --exceptions <file> seals such a file into the evidence bundle, alongside the before/after scans, the plan and the manifest of checksums. Pavois does not impose a schema; an auditor expects, at minimum, per entry: the control id, the host, the reason, the compensating measure, the expiry date, and who approved it. A dated exception with a compensating control is a decision; an undated one is a hole with paperwork.
FAQ
Can I scan without root? No, not meaningfully. sshd -T refuses, auditctl -l refuses, /etc/shadow refuses. A non-root scan would report a shape of the system, not its effective configuration, which is the one thing Pavois exists to read.
Can I restrict sudo to one binary? With --on-target, yes: cinc-auditor. Understand that it then runs as root; you have narrowed what a stolen credential can invoke, not what the scan can do.
Does Pavois phone home? No. The only thing it ever fetches is the CINC installer from omnitruck, and only when you pass --bootstrap-cinc, on a scan and on an apply alike. Without that flag it stops and prints the command it would have run. Nothing else ever leaves the host.
Why is --on-target not the default? Because it needs the engine installed on the target. The SSH mode works against a host with nothing on it, which is the right default for a first contact.
Can Pavois harden a running production server? Partly, and it will tell you which part. Everything but install-time and kernel-build applies in place, and the remediable grade is exactly the score of what it can reach without a rebuild. The rest is an image problem, and it should be solved where images are made.
Key takeaways
- Pavois is a tool for building a compliant image, and for detecting drift from it. It is not a retrofit for a legacy host, and the
install-time/kernel-buildclasses are where that truth is written down. - Root is required, because effective configuration is privileged.
--on-targetlets you scope sudo to one binary, and is 7 times faster (13s versus 90s on 620 controls). - Air-gap is a solved problem: pre-install CINC in the image, point package remediations at an internal mirror, and Pavois never reaches the network.
- A waiver is Pavois's accepted risk. Your exception is a dated, approved, compensated business decision, and it belongs in the evidence bundle.