← All rules
// Hardening (posture)higheffective runtime

The audit trail cannot grow until a stopping action fires

A host can be told to halt when its audit partition fills, and separately be left with an audit trail that grows without bound. Each setting is compliant on its own, and together they do not protect the host: they schedule its shutdown. This pavois-native control of the growth-* family measures the combination. When admin_space_left_action or disk_full_action can stop the machine, the trail must be bounded: max_log_file_action = rotate with num_logs > 1 and a non-zero max_log_file, or suspend with a non-zero max_log_file, which stops the logging rather than the machine.

Checked against the resolved running state (e.g. sshd -T, sysctl, systemctl show), catches drop-ins and Includes a file read would miss. Caveat: runtime ≠ persistence; a value correct now may not survive a reboot.

A pass proves✓ running now✓ on disk✓ survives rebootthe qualified verdict →
Debian 12CIS 1.1.0Debian 13CIS 1.0.0FedoraRHEL 10 / Rocky 10 / AlmaLinux 10RHEL 8 / Rocky 8 / AlmaLinux 8CIS 4.0.0RHEL 9 / Rocky 9 / AlmaLinux 9CIS 2.0.0Ubuntu 22.04CIS 3.0.0Ubuntu 24.04CIS 1.0.0Ubuntu 26.04
One check, maps to 1 standard

Pavois asserts the effective configuration, the live, resolved state, not a file. File-based scanners (OVAL/SCAP, Lynis) miss Includes, drop-ins and runtime defaults; this check sees what is actually applied.

A mapping is a cross-reference to where each standard places this requirement, anchored and cross-validated, not a claim of equivalence. A passing check is evidence toward these references, how to read it.

Why this rule matters

A fail-closed action on the audit subsystem is a safety net only if the thing it catches is rare. Against an unbounded trail it is not a safety net, it is a countdown: the trail grows at the rate the host is audited, reaches the threshold, and the action fires. Nothing in the configuration is wrong when read one directive at a time, which is exactly why this has to be checked as a pair. The norms make the same demand from both sides, AU-4 asking that audit storage be sized to the retention it requires, and the DISA STIG asking that the host stop rather than audit nothing, and a host can satisfy both texts while being unable to stay up.

What Pavois checks

Pavois reads /etc/audit/auditd.conf and keeps the last occurrence of each directive, which is the value auditd loads at start and at reload. Reading a file is normally what pavois refuses to do for a service, because an Include or a drop-in would make the file lie. auditd.conf is the exception with a reason: unlike the audit rules (/etc/audit/rules.d/), it has no drop-in directory and no aggregation step, so the single file is the effective configuration, and the daemon offers no -T style dump to compare it against.

Five directives are read, and the verdict is a decision, not a threshold:

  • if neither admin_space_left_action nor disk_full_action can stop the host (halt, single), the control passes and says so: there is no combination to arbitrate.
  • otherwise the trail must be bounded, by rotate with num_logs > 1 and max_log_file > 0 (worst case num_logs × max_log_file MiB), or by suspend with max_log_file > 0.
  • an absent directive is treated as auditd's own documented default (max_log_file 0, num_logs 0, action ignore), which is not the same thing as a value that could not be read: a non-numeric size or count reports unknown and fails. This control never passes on evidence it did not gather.

One limit, deliberate and stated rather than hidden: the check reads auditd.conf only. A trail bounded at num_logs × max_log_file on a partition smaller than that bound is still an outage waiting to happen, and is not detected, because deciding it needs df, du and five times the code of the longest check in this base.

describe command('sh -c \'v() { grep -iE "^[[:space:]]*$1[[:space:]]*=" /etc/audit/auditd.conf 2>/dev/null | tail -1 | sed -E "s/^[^=]*=[[:space:]]*//; s/[[:space:]]*(#.*)?$//" | tr A-Z a-z; }; a=$(v admin_space_left_action); f=$(v disk_full_action); m=$(v max_log_file_action); n=$(v num_logs); s=$(v max_log_file); case "$a$f" in *halt*|*single*) ;; *) echo "ok no stopping action"; exit 0;; esac; n=${n:-0}; s=${s:-0}; case "$n$s" in *[!0-9]*) echo "unknown num_logs or max_log_file not numeric"; exit 0;; esac; if [ "$m" = rotate ] && [ "$n" -gt 1 ] && [ "$s" -gt 0 ]; then echo "ok bounded at $((n * s)) MiB"; elif [ "$m" = suspend ] && [ "$s" -gt 0 ]; then echo "ok bounded at $s MiB"; else echo "ko UNBOUNDED trail (max_log_file_action=${m:-ignore} num_logs=$n max_log_file=$s) with a stopping action (admin_space_left_action=${a:-ignore} disk_full_action=${f:-ignore})"; fi\'') do
  its('stdout') { should match(/^ok /) }
end

How to verify it is applied

Read the five directives that decide, then the space they have to fit in:

grep -iE '^[[:space:]]*(admin_space_left_action|disk_full_action|max_log_file_action|num_logs|max_log_file)[[:space:]]*=' /etc/audit/auditd.conf
df -h /var/log/audit

A bounded host answers with something like:

admin_space_left_action = HALT
disk_full_action = HALT
max_log_file_action = ROTATE
num_logs = 5
max_log_file = 8

Read it as one sentence: the host stops when audit space runs out, and the trail can never claim more than 5 × 8 = 40 MiB, which must fit in what df reports for /var/log/audit. The same output with max_log_file_action = keep_logs, or with num_logs = 0, describes a host that will stop itself sooner or later.

Inspect & investigate

journalctl -u auditd records the space thresholds being crossed and the action auditd took, and ls -1 /var/log/audit/audit.log* plus du -sh /var/log/audit say how much of the partition the trail already holds.

The failure this control prevents has a signature worth knowing, because it denies you the live system: a host that powers itself off a few seconds into every boot. auditd starts, finds the partition full, applies the stopping action, and the machine is gone before anyone can log in. The evidence is in the previous boot, so read journalctl -b -1 -u auditd from a rescue or console session rather than trying to catch it live.

Remediation

The remediation is manual by design, and it is the rare case where pavois declines to choose for the operator. On a DISA STIG host halt is the required value, so silently rewriting it would trade an availability incident for a compliance deviation nobody asked for. Four outcomes satisfy this control, and they are policy decisions:

  1. Bound the trail: max_log_file_action = rotate, num_logs > 1, max_log_file sized so num_logs × max_log_file fits the partition. The oldest records are discarded, so choose this only if the retention requirement allows it or the trail is shipped elsewhere.
  2. Enlarge /var/log/audit, which buys time proportional to the enlargement and does not change the failure mode.
  3. Ship the trail off the host (audispd remote plugin, or a collector), which is the only option that keeps both an unbounded retention and a host that stays up.
  4. Choose an action the applicable norm still accepts, for instance suspend or syslog where the standard permits it.

Note what the remediation does not require: none of auditd-admin-space-left-action, auditd-disk-full-action or auditd-max-log-file-action has to become non-compliant. The control measures a combination, so it is cleared by changing the combination, not by disabling a guard.

Pavois applies this with its own harden engine, the plan below, not a shell script:

reasonThe operator chooses: bound the trail (max_log_file_action = rotate with num_logs > 1 and a max_log_file), enlarge /var/log/audit, ship the journal off the host, or set an action the applicable norm still accepts. Pavois will not pick for them: on a STIG host halt is the REQUIRED value, so silently rewriting it would trade an availability incident for a compliance deviation the operator did not ask for.
resourcemanual
pavois harden plan local

where the target is local, a user@host SSH alias, or a container , Docs

Impact & precautions

The growth-* family exists because of a real outage, and this control is the one that would have caught it. A pavois-hardened host powered itself off six seconds into every boot: max_log_file_action = keep_logs (auditd never deletes a log), a separate /var/log/audit partition created by pavois's own recipe, and admin_space_left_action = halt. The filesystem was measured at 738 files, 5.8 GB, 100% full, and every control was green, because each of the three was individually compliant.

The trade-off differs per outcome. Bounding the trail discards the oldest records, which is a real loss on a host whose whole purpose is to keep them, and is the reason keep_logs looks attractive in the first place. Enlarging the partition only moves the date. suspend keeps the host up but stops recording, which for most standards is a deviation of its own rather than a fix.

Precaution before changing anything: the host that fails this control may already be close to the threshold. Check df -h /var/log/audit first, and reclaim or archive space before reloading auditd, because a reload on a nearly full partition can fire the very action you are trying to make unreachable.

Standards mapping

StandardReferenceTypeVersionConfidence
NISTAU-4supporting800-53 Rev 5 · 800-171 Rev 2 (pinned)medium

Each reference is a cross-reference anchored in the upstream benchmark and cross-validated against the SCAP Security Guide and ansible-lockdown, not a claim of equivalence. Direct = a prescriptive, line-level requirement; supporting = an abstract control family (NIST) the check provides evidence toward. How to read a mapping.

Sources & references