Handbook sections

Operating 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:

  1. Build: a fresh cloud image, the partition recipe, the kernel recipe, harden apply converged. That is your golden image, and its grade is the grade your fleet starts from.
  2. Ship: bake it (Packer, an image pipeline, a template).
  3. Watch: scan the running fleet to detect drift from the image you shipped, and harden apply the 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: false

That 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-build classes are where that truth is written down.
  • Root is required, because effective configuration is privileged. --on-target lets 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.

Next steps