Custom audits let you check your servers against your own standards, next to the CIS benchmarks on the Hardening tab. An audit is a named list of checks for one platform (Linux, Windows or macOS). Each check looks at one thing on the server and passes or fails. You assign an audit to every server of its platform, or to chosen scan groups, and read the results per audit, per check and per server. If you are moving from Tenable, you can also import the parts of your existing .audit files that can be reproduced faithfully. See Import a Tenable audit file.

What a check can and cannot do

A check is declarative and read-only. It can look at:
  • whether a file exists, its mode, owner and group;
  • whether a file’s lines match, or do not match, a regular expression, optionally comparing a value captured from the line;
  • a Windows registry value under HKEY_LOCAL_MACHINE;
  • a Windows password, lockout, audit or user-rights setting;
  • a Linux kernel parameter (sysctl);
  • a service’s configured start state (systemd, Windows services, launchd);
  • whether an operating-system package is installed.
There is no way to run anything. The format has no field for a command, a shell string, PowerShell or a WMI query, and the agent has no code path that starts a process on behalf of an audit. A document that contains an unknown field or test type is rejected as a whole.

Requirements

  • A server running the agent with the server profile, in an agent release that includes custom audits. A server on an older agent shows Not assessed (agent too old) for the audit, never a pass. See Update an existing agent.
  • The Custom audits collector enabled in the organization’s collector policy. It is on by default.
  • Windows policy checks (password_policy, lockout_policy, audit_policy, user_right) read the facts the hardening collector already gathers, so they need the agent in privileged mode. Without it they show Not assessed.
  • An owner or admin role to create, import, edit or assign an audit. Every member can read audits and results.

Create an audit

Open Infra Vulnerabilities, then Hardening, then Custom audits. Choose New audit, name it, pick the platform, and edit the checks as JSON. Check validates the document without saving it and lists every problem at once. The platform cannot be changed after creation, because results and assignment are per platform. A new audit reaches no server until you assign it. Choose Assign, then either all servers of the audit’s platform or specific scan groups. A scan group that is disabled matches nothing. The assignment takes effect at each server’s next scan. Editing the checks raises the audit’s version. Each server reports which version it judged, and a result from an earlier version is shown as such until the server scans again. Renaming an audit, enabling or disabling it, and changing its assignment do not change the version.

The format

An audit’s checks are a JSON object with a checks list.
{
  "checks": [
    {
      "id": "ssh-root-login",
      "title": "sshd does not permit root login",
      "severity": "high",
      "remediation": "Set PermitRootLogin no in /etc/ssh/sshd_config and reload sshd.",
      "test": {
        "type": "file_content",
        "path": "/etc/ssh/sshd_config",
        "match": "present",
        "pattern": "^\\s*PermitRootLogin\\s+(\\S+)",
        "capture": { "op": "in", "values": ["no", "prohibit-password"] }
      }
    }
  ]
}
Every check has an id (lower case letters, digits, dot, dash and underscore, at most 64 characters, unique in the audit), a title, and a test. It can also carry severity (low, medium, high or critical), description, remediation, reference and when. A field that a test type does not use is an error, not ignored, so a check can never pass on a condition its author thought it also verified.

Test types

TypePlatformsFieldsPasses when
file_existsallpath, existsat least one file matches path (exists: true), or none does (exists: false)
file_contentallpath, match, pattern, expect, min_matches, capture, missingsee file content
file_modeLinux, macOSpath, exact_mode or max_mode, owners, groups, missingevery matched file has the mode, owner and group given
registryWindowskey, value_name, exists or compare, missingthe value exists, or satisfies the comparison
password_policyWindowssetting, compare, missingthe setting satisfies the comparison
lockout_policyWindowssetting, compare, missingthe setting satisfies the comparison
audit_policyWindowssubcategory, compare, missingthe audit setting satisfies the comparison
user_rightWindowsright, holdersthe accounts holding the right match holders
sysctlLinuxkey, compare, missingthe kernel parameter satisfies the comparison
serviceallname, statesthe service’s configured state is one of states
packageLinuxname, existsthe package is installed (true) or not (false)

Comparisons

A compare has an op and a value:
opValueMeaning
eq, nevalueequal or not equal. Numbers compare as numbers; text ignores case and runs of whitespace.
lt, le, gt, gevalue, a numbernumeric comparison. A value that is not a number is Not assessed, not a failure.
betweenvalue as "low..high"numeric, inclusive on both ends
matches, not_matchesvalue, an RE2 expressionregular expression match
in, not_invalues, a listthe value is, or is not, one of the list
Regular expressions use the RE2 syntax and are limited to 512 characters. RE2 has no lookahead, lookbehind or backreferences; a pattern that uses them is rejected when you save it.

File content

file_content reads a file line by line. pattern selects lines. With match: "present", at least min_matches lines (default 1) must match, and if expect is given, every selected line must also match expect. With match: "absent", no line may match pattern and expect. With capture, the pattern needs exactly one capture group and its text is compared with the capture comparison. When path contains a wildcard, the matched files are judged together as one stream, so a setting may be in any of /etc/modprobe.d/*.conf. A missing value of pass, fail, not_applicable or not_assessed says what a missing file or setting means; the default is fail.

Paths and wildcards

A path is absolute. On Windows it starts with a drive (C:\) or one of %SystemRoot%, %SystemDrive%, %ProgramFiles%, %ProgramFiles(x86)%, %ProgramData% and %windir%. * and ? are allowed in at most two path segments, never in the first, and ** is not supported. .. is not allowed. These rules, with the limits below, are what keep a wildcard from becoming a walk of the whole disk.

Windows policy settings

Testsetting values
password_policymin_length, max_age_days, min_age_days, history_size, complexity, reversible_encryption
lockout_policythreshold, duration_minutes, reset_minutes
audit_policy takes a lower-case subcategory name such as credential_validation or logon, and compares against none, success, failure or success_failure. The agent reports the subcategories it collects; any other shows Not assessed. user_right takes a right such as SeInteractiveLogonRight and holders with an op of none, only, includes or excludes and a list of SIDs. Rights are compared by SID, not by account name.

Service states

states is a list of enabled, disabled, manual, masked and not_installed. This is the configured state: whether the service starts at boot, read from the systemd unit files, the Windows service start type, or the launchd override database. It does not say whether the service is running at this moment.

Applicability

A check can carry a when block so it only applies where it makes sense:
"when": {
  "match": "all",
  "conditions": [ { "type": "package", "name": "nginx", "exists": true } ]
}
match is all or any, and negate: true inverts the result. When the condition is not met the check is Not applicable. When the condition cannot be decided, the check is Not assessed.

Results

ResultMeaning
PassThe server meets the check.
FailIt does not. Evidence shows the observed value where one can be shown.
Not applicableThe check’s when condition is not met, or its missing setting says so.
Not assessedThe check could not be judged on this server: a path on the deny-list, a limit that was reached, a setting the host did not report. This is unknown, never a pass.
Each server is also in one of these states for each audit:
StateMeaning
AssessedThe server judged every check of the current version.
Assessed against an older versionIt judged an earlier version and will re-judge at its next scan.
Waiting for the next scanIt has not scanned since the audit was assigned.
Not assessedThe server could not judge the audit as a whole; the reason is shown.
Not assessed (agent too old)Its agent has never asked for custom audits, so it predates the feature. Upgrade the agent. This is not a pass.
Not deliveredA server receives at most 1000 checks across all its audits, and this audit did not fit. Narrow the assignment or split the audits. This is not a pass.
Servers in the last four states are listed separately in the audit’s results and are never counted as passing.

What leaves the server

The agent judges each check on the server. Only these leave it:
  • the verdict;
  • a short reason, chosen from a fixed list of sentences;
  • evidence of at most 256 characters, with credential-shaped content masked.
File contents never leave the server. A content check reports the number of matching lines and where the first one is, not the lines. Evidence shows the observed value only where it is a number or an on/off switch, a file mode and owner, a kernel parameter, a service state or a package version. A registry string value is never shown, only its length, and a captured text value is never shown.

What the agent will never read

An audit is delivered from the platform to every assigned server, so the agent enforces limits that no document can change. A verdict alone is not safe for a file that holds a secret: a check that asks whether the second field of a password-hash line starts with a given prefix reveals the hash one bit at a time. The agent therefore refuses the file, whatever the check says.
ClassWhat it coversWhat a check can do
Never touchedThe agent’s own state and credentials; the Windows registry hive files (SAM, SECURITY, SYSTEM, SOFTWARE) and their backups; Active Directory’s database; DPAPI keys and credential vaults; browser profiles; keychains; /proc/<pid> state such as environ and mem; /dev; container and orchestrator secret mountsnothing, not even whether the path exists
Content never read/etc/shadow and /etc/gshadow; private keys and key stores (*.pem, *.key, *.p12, *.pfx, *.jks, SSH private keys); .netrc, .pgpass, cloud and Kubernetes credential files; dotenv files; shell historyexistence, mode, owner and group only. This is what lets you check that /etc/shadow is mode 0640.
Registry keys HKLM\SAM and HKLM\SECURITY can never be read. A registry value whose name looks like a credential (for example one containing password or secret) is compared only when it is a number. Symbolic links are followed before the deny-list is applied, so a link to a denied file is denied. Three more rules apply to file content:
  • A line that assigns a credential-shaped setting, such as db_password = ... or api_token: ..., is never evaluated: no pattern is run against it, so no verdict can depend on it. A setting with a switch or a small number as its value, such as PasswordAuthentication no or MinimumPasswordLength = 14, is still audited. The evidence says how many credential-shaped lines were skipped.
  • A file that looks binary, or that begins like key material, is not read.
  • A check cannot be satisfied or violated by a skipped line. This is the cost of the protection, and it is why the protection is by line and not by file.

Limits

The console enforces these when you save, and the agent enforces them again. The agent rejects the whole document if any is exceeded.
LimitValue
Audits per organization25
Checks per audit200
Checks delivered to one server1000
Conditions in one when8
Regular expression length512 characters
Path length1024 characters
Bytes read per file1 MiB (a file larger than this is judged only as far as it proves something)
Files per wildcard64
Entries listed per directory5000
Files read per scan2000
Bytes read per scan32 MiB
Time for all custom audits in one scan60 seconds
Evidence per check256 characters
A check that cannot finish within a limit is Not assessed, never a pass. A violation found before a limit was reached still fails; a file too large to read to the end cannot prove that a setting is absent, so that case is Not assessed.

Import a Tenable audit file

Choose Import Tenable audit, then choose the file or paste its text. The import is a preview first: every item in the file appears in a table, marked Imported or Skipped with the reason, and nothing is saved until you confirm. Nothing is silently dropped. The imported audit is not assigned to any server until you assign it, and the skipped items are kept with the audit so you can still see them. Only Unix and Windows files can be imported. A Unix file becomes a Linux audit by default; you can choose macOS.

What is imported

Tenable itemBecomes
FILE_CHECK with owner, group and mode or mask (Unix)file_mode. A mask is read as a umask: the maximum mode allowed is its complement. `` alternatives become a list.
FILE_CHECK_NOT with only file (Unix)file_exists with exists: false
FILE_CONTENT_CHECK with regex and expectfile_content present: the lines regex selects must also match expect. min_occurrences becomes min_matches.
FILE_CONTENT_CHECK_NOTfile_content absent
RPM_CHECK with an explicit required: YES or NO and no versionpackage installed or not installed
REGISTRY_SETTING and REG_CHECK under HKLMregistry. POLICY_DWORD ranges such as [1..30] and [15..MAX], sets, POLICY_TEXT, and POLICY_SET (Enabled is read as 1 and Disabled as 0). CAN_BE_NULL becomes missing: pass.
PASSWORD_POLICY and LOCKOUT_POLICYpassword_policy, lockout_policy
AUDIT_POLICY_SUBCATEGORYaudit_policy
USER_RIGHTS_POLICYuser_right, when every account is a SID or a well-known group such as Administrators or Authenticated Users. No One becomes none.
SERVICE_POLICYservice, comparing the start type
FILE_CHECK with file_option MUST_EXIST or MUST_NOT_EXIST (Windows)file_exists
<if>, <condition>, <then>, <else>a when block on the items in each branch; the <else> branch is the negation. A forced PASSED report is noted: the checks outside a taken branch report Not applicable, which stands in for it.
description becomes the title, info the description, solution the remediation, reference and see_also the reference, and severity the severity. The check’s id is generated from its position and title.

What is skipped, and why

SkippedReason
CMD_EXEC, AUDIT_POWERSHELL, WMI_POLICY, AUDIT_XML, MACOSX_DEFAULTS_READ, and any item with a cmd, powershell or WMI fieldCommand checks are not supported. A check that runs something is exactly what a compromised console account would use to attack every server, so the format cannot express it.
An <if> whose condition cannot be importedIts branches are skipped with the reason, because they would otherwise run without the guard.
A forced WARNING or FAILED reportResults come from the checks themselves.
Content checks on a path on the deny-list, such as /etc/shadowThe content is never read.
A regular expression that RE2 cannot compileLookahead, lookbehind and backreferences.
PROCESS_CHECK, open-port checks, CHKCONFIG, XINETD_SVCThey need a process or socket listing, or run levels, which are not supported.
PKG_CHECK, items for another system such as SunOSThe agent does not cover that platform.
RPM_CHECK with a version or an operator, or without requiredVersion comparison is not supported, and without required the meaning is ambiguous.
Registry values under HKU or other per-user hives, reg_enum, binary, multi-string and ACL valuesThey are not the server’s configuration, or cannot be compared.
Windows ACL, md5, uid, gid and attribute checksNot supported.
BANNER_CHECKUse a file_content check on the banner file or a registry check on the legal notice value.
An account name with no well-known SIDThe agent reports SIDs, not account names, so the comparison cannot be made faithfully.
Any field that is not understoodA field that carries meaning is never ignored, because an item that imports but means something else gives a false pass or a false failure.
Backslashes in quoted strings are read as in the audit file: \\ is one backslash and \" is a quote. Regular expressions written for a POSIX engine usually work as they are.

Audit trail

Creating, editing, deleting, importing and assigning an audit each write an entry to the organization audit log with the user, the audit and a summary of the change. Documents are not copied into the log. An imported audit records the digest of the file it came from.