Handbook sections

How the A-E grade is computed

Last reviewed

A single A-E letter is only worth trusting if you can recompute it by hand. Pavois publishes the full formula: severity-weighted penalties, per-band caps, and a critical failure forces grade E.

An A-E letter is a strong claim, and a strong claim has to be auditable. A grade that can stay green while a critical control fails is worse than no grade. So Pavois publishes its formula in full, computes it identically in the CLI binary and in the HTML report, and freezes it behind a test (go/internal/audit/audit_test.go + tools/validate_grade.py keep the two engines in sync).

The formula

The grade is computed only over scored controls: anything skipped or not-applicable on the target is excluded from both the numerator and the denominator, so a control that doesn't apply never helps or hurts the grade.

Start at 100 points and subtract a penalty for each failed control, weighted by severity:

  • critical: 25 points each
  • high: 15 points each
  • medium: 6 points each
  • low: 3 points each

The loss from each severity band is capped, so a long tail of low-severity findings can't alone sink the grade:

  • high loss capped at 60
  • medium loss capped at 20
  • low loss capped at 10
  • critical loss is never capped

The critical-failure rule

This is the part that defeats the "a B at 82% hides a disaster" objection. Any single critical failure caps the score at 30 points (grade E), regardless of how many other controls pass. A host with one critical gap is not a B host, whatever its percentage.

The bands

Points Grade
90-100 A
71-89 B
51-70 C
31-50 D
0-30 E

Worked examples

1, a near-clean host. 2 medium fail, 1 low fail, everything else passes: 100 - (2x6 + 1x3) = 85, grade B.

2, the hidden-critical attack. 1 critical fails (remote root login re-enabled by a drop-in) while 120 low-severity controls pass. A naive pass-rate would read ~99% and call it an A. Pavois does not score a pass-rate: the subtractive score is 100 - 25 = 75 on its own, and then the critical-failure rule caps it to 30, grade E. Two independent reasons the critical gap cannot hide behind a wall of green.

3, death by highs. 5 high-severity controls fail, nothing critical: raw loss is 5x15 = 75, capped at 60, score 40, grade D.

The grade in JSON

--format json emits the grade and the counts that produced it, so CI can gate on it and you can recompute it. A scan with one critical and three lesser failures (subtractive score 100 - 25 - 12 - 3 = 60, then the critical cap drops it to 30):

pavois scan local --profile profiles/linux/debian12 --format json --sudo
{
  "grade": "E",
  "points": 30,
  "passed": 146,
  "total": 150,
  "runtime_qualified": false,
  "qualified_passes": 0,
  "counts": { "critical": 1, "high": 0, "medium": 2, "low": 1 },
  "posture": { "remediable_grade": "C", "remediable_passed": 130, "remediable_total": 138 }
}

The same numbers drive the headline line in the terminal and the HTML report, and feed the JSON evidence export (OSCAL assessment-results is on the roadmap).

What the grade does NOT yet encode

Honesty cuts both ways. Two limits to keep in mind:

  • Severity drives the cap, so the rating of a control matters. Pavois currently rates 13 controls critical (remote root login, empty SSH passwords). Severity is derived from each control's impact (impact >= 0.9 is critical), and severities are open to review: if you believe a control in your context deserves to force an E (say, audit logging disabled on a regulated host), its rating is the lever.
  • The grade scores the evidence each control gathers, with the caveats from its evidence type. A passing effective-runtime control proves the running state, not that the setting survives a reboot. Each fiche shows a per-control "a pass proves" matrix (running now / on disk / survives reboot) derived from the evidence type and exported in the OSCAL catalog (proves-running, proves-persistent, proves-reboot-survivable). The grade weights this: a runtime-only PASS whose persistence is unproven does not count as a full pass, so it caps the letter (a clean A is withheld and the grade is shown runtime-qualified, that is, capped to B), while the failure-driven points are unchanged. pavois harden apply --reboot --scan re-scans after a real reboot, so a pass there is reboot-proven. See the qualified verdict for the full model.

The formula is deliberately simple and public. If it ever changes, the frozen test breaks on purpose and the change is recorded: a grade you can recompute by hand is the only kind worth trusting.

Posture by remediation class

The grade scores every applicable control the same way, but not every failure is fixable on a running host: a separate /var partition needs a reinstall, and a hardened kernel CONFIG_* needs a kernel rebuild. So every scan also reports a posture breakdown by remediation class, auto, manual, dangerous, install-time and kernel-build, plus a remediable posture grade: the same A-E formula recomputed over only the controls you can fix on this host (it excludes install-time and kernel-build).

This reframes a result honestly. On a freshly hardened Debian 12, of 68 remaining failures 46 are install-time (24) plus kernel-build (22), so only 22 are actually remediable without reinstalling or rebuilding the kernel. The breakdown is printed under the grade, emitted in --format json under posture, and recorded in the evidence bundle manifest.

FAQ

Can a host with one critical failure still get a B? No. A single critical failure caps the score at 30 points, grade E, regardless of how many other controls pass. That is the whole point of the critical-failure rule.

Why is a runtime-only pass capped to B? Because it proves the value is in force now, not that it survives a reboot. A clean A requires persistence-proven evidence; a runtime-qualified scan is capped to B until a post-reboot re-scan confirms it.

Can I recompute the grade myself? Yes. Start at 100, subtract the capped per-severity penalties, apply the critical-failure cap, read the band. The formula is frozen behind a test so the CLI and HTML never silently diverge.

Why is my grade C for things I cannot fix without reinstalling? It is not: alongside the overall grade, pavois reports a remediable posture grade that excludes install-time and kernel-build controls, so you see what is actually fixable on this running host.