Skip to content

HDF Status Determination

How hdf-libs determines the overall status of a control/requirement from its individual test results. This logic applies to all data sources (InSpec, SARIF, Nessus, XCCDF, etc.).

Status Values

StatusMeaning
passedControl requirements are met
failedOne or more requirements are not met
errorA test execution error occurred
notApplicableControl is not applicable (impact 0.0 or explicitly scoped out)
notReviewedControl was not evaluated (skipped, no results)

Precedence Order

When a control has multiple test results with different statuses, the overall status is determined by precedence (highest wins):

1. error          — any result errored → control is error
2. failed         — any result failed  → control is failed
3. passed         — any result passed  → control is passed
4. notApplicable  — only if all results are notApplicable
5. notReviewed    — only if nothing else matches (all skipped/notReviewed)

Key Rules

  • failed + passedfailed: One failure means the control fails
  • passed + notReviewedpassed: A passing test proves the control works; a skipped test alongside it doesn't negate that
  • impact === 0notApplicable: Overrides all result statuses. A control with impact 0.0 is always Not Applicable regardless of whether its tests passed, failed, or were skipped
  • No results → notReviewed: Unless impact is 0.0 (then notApplicable)

Examples

ResultsImpactOverall Status
[passed]0.7passed
[failed]0.7failed
[passed, failed]0.5failed
[passed, notReviewed]0.7passed
[notReviewed]0.5notReviewed
[notReviewed]0.0notApplicable
[passed, error]0.5error
[] (empty)*0.0notApplicable
[] (empty)*0.5notReviewed

* The v3 HDF schema enforces results.minItems=1, so empty results arrays should not appear in schema-valid HDF documents — converters synthesize a passed placeholder for clean scans (see ../specification/hdf-specification.md § "Clean-scan convention"). The empty-results rows above remain a safety net for legacy HDF v1 (InSpec-ExecJSON-shaped) input that did not enforce the invariant.

Where Status Is Computed

effectiveStatus Field

The effectiveStatus field on EvaluatedRequirement is the authoritative status. When present, consumers should use it directly. When absent, consumers derive status from results using the precedence above.

In hdf-libs

  • hdf-converters (convertControl): Sets effectiveStatus from the source data. For v1 InSpec data, also sets effectiveStatus = notApplicable when impact === 0.
  • hdf-cli (determineControlStatus in stats.go): Checks effectiveStatus first (via SchemaStatusToDisplay mapping). Falls back to result-based derivation with correct precedence.

Schema Note

The HDF schema uses camelCase for status enum values (notApplicable, notReviewed). The CLI uses snake_case for display (not_applicable, not_reviewed). The SchemaStatusToDisplay function in the CLI handles this translation.

Overrides

Security results data often has to distinguish between the original status of a requirement as tested (e.g. the status the scanning tool originally reported) versus contextualization applied to that status later (an override, applied after the results are already collected). Many source formats for HDF have some concept of an override for results. CVE data, for example, often includes the Base components of a CVSS score but assumes that the consumer of the scan will fill out the rest of the CVSS metrics to give a complete picture of the risk a CVE represents in-context for a given environment.

Many security functions also allow a failing requirement to be considered waived, meaning that while the result as-tested is still a failure, it does not represent a failure that the organization will fix at this time, usually due to a mitigating control or simple risk acceptance. A given requirement might have multiple overrides applied to it over time.

All of these nuances require that HDF has fields to represent both the original status of a requirement, and the effective status of that requirement after all overrides have been applied to it.

The precedence rules above answer one question — did the requirement pass or fail as tested? A waiver, false-positive determination, or risk acceptance answers a different question — has that result been formally adjudicated? These are two orthogonal axes, and HDF keeps them distinct:

  1. Verdict (the raw result). The status rolled up from results[] by the precedence above. This is what the scan actually observed. An acceptance decision never rewrites it.
  2. Acceptance (the override). A consumer-attached statusOverride records a governance decision about a raw failure. It is carried in disposition, effectiveStatus, effectiveImpact, statusOverrides[], and poams[] — alongside, not instead of, the raw results.

A waiver does not make a control pass. The raw verdict stays failed; the waiver is recorded on the separate acceptance axis. Collapsing the two — treating a waived failure as a genuine pass — is an audit-integrity anti-pattern: a reader of the verdict can no longer tell "genuinely compliant" from "failed but accepted," and actionable-failure counts silently drop as risks are accepted.

effectiveStatus is the post-adjudication status, not the verdict

With no override, effectiveStatus equals the raw roll-up — they are the same value. An override is the only thing that makes them differ. When they differ, effectiveStatus is the post-adjudication status and the raw roll-up (worstOf(results[].status)) is still available losslessly in results[]. Which one a consumer should key on depends on the question it is answering (see disposition branching and the SIEM export guide).

Disposition branching

Not every override suppresses the finding. The disposition (the governing override's type) determines whether the finding leaves the actionable set or merely gets re-scored:

DispositionTypical effectiveStatusStill an open finding?Meaning
falsePositivepassed / notApplicableNo — genuinely closedThe scanner was wrong; the check actually passes or does not apply
waiverpassed / notApplicableAccepted out of the actionable setRisk formally accepted by an Authorizing Official
attestationpassed / notApplicableAccepted out of the actionable setManually verified compliant by an assessor
riskAdjustmentfailedYes — stays openOnly the impact/severity is re-scored (environmental context); the finding remains
operationalRequirementfailedYes — stays openCannot be remediated (operational constraint); remains an accepted open risk
poamfailedYes — stays openRemediation is tracked; status is unchanged until the work lands

The dividing line is whether the override drives effectiveStatus to a non-failing value. falsePositive, waiver, and attestation do; riskAdjustment, operationalRequirement, and poam leave the finding failing and actionable.

Standards alignment

The two-axis model is not an HDF invention — it mirrors how the governing frameworks separate assessment from risk response:

  • NIST SP 800-53A / RMF (SP 800-37): control assessment (Satisfied vs Other-Than-Satisfied) and risk response (accept / mitigate / transfer) are distinct steps. Accepting a risk does not make the control Satisfied.
  • FedRAMP deviation requests: a Risk Adjustment or Operational Requirement leaves the finding Open in the POA&M; only the risk rating or remediation obligation changes. A False Positive is the deviation that closes it.
  • OSCAL Assessment Results: finding.target.status (the objective result) and finding.associated-risk[].facet (the risk response) are separate, independently-persisted axes — findings are not deleted by risk acceptance.
  • VEX: not_affected records a vulnerability as present but not exploitable in context — the affected component still ships; the finding is contextualized, not erased.
  • GRC / SIEM tooling (OCSF, AWS Security Hub, Rapid7, Sysdig): all model Suppressed ≠ Resolved — suppression is an acknowledgement axis separate from the finding's own state.

Reference

This logic matches InSpec Enhanced Outcomes (Inspec::EnhancedOutcomes.determine_status), which is the industry standard for security control status determination.

Released under the Apache 2.0 License.