OpenSCAP
OpenSCAP is the open-source engine behind NIST's Security Content Automation Protocol (SCAP) — a single command-line tool, oscap, that evaluates machine-readable compliance content against a running host, a container image, or an offline virtual-machine disk, and reports back exactly which checks passed, which failed, and which couldn't be automated at all. Its whole reason for existing is a specific trade this course's own compliance-as-code chapter puts right next to InSpec: instead of a team hand-writing every control as code and hoping their reading of a benchmark matches the standards body's intent, OpenSCAP runs content someone else already published, tested, and versioned — the SCAP Security Guide's community benchmarks, or, in the environments where it matters most, DISA's own Security Technical Implementation Guides (STIGs), where each rule already carries a Control Correlation Identifier (CCI) tracing back to a specific NIST SP 800-53 control. That regulatory mapping ships baked into the content itself, not bolted on afterward by whoever happened to write the check — which is exactly the gap a hand-rolled InSpec profile has to close manually, one tag at a time, if it wants the same audit trail. By the end of this page you should know SCAP's component standards and how oscap consumes them, where DISA STIG content fits and why regulated shops reach for it specifically, the commands you'll actually run, the ways a scan can quietly report a false "clean," and precisely where this standards-based approach outruns InSpec's DSL — and where it doesn't.
Imagine two ways to get your bike ready for a safety inspection. One way: you sit down and write your own checklist from memory of what you think the inspector cares about, then check your bike against your own list. The other way: the inspector's office publishes their actual inspection form, box by box, updated every few months — and you run your bike through their form instead of yours. OpenSCAP is the second way. It hands your computer the official checklist instead of asking you to reconstruct one from memory, and if anyone ever asks "why did you check this exact box," the form itself already answers, on the inspector's own authority, not yours.
What OpenSCAP is, and the problem it solves
☺ Like you're 10: It's a reader for a very specific kind of official checklist — one written in a format a computer can run by itself, not one a person has to walk through by hand.
SCAP — the Security Content Automation Protocol — is a suite of open specifications maintained by NIST for expressing security checks and configuration benchmarks in a machine-readable, vendor-neutral way. NIST doesn't ship a scanner itself; it defines the formats and runs a formal validation program that certifies specific vendor tools as SCAP-capable for specific purposes. OpenSCAP is the leading open-source implementation of that suite: a C library (libopenscap) plus a command-line front end, oscap, developed primarily by Red Hat and released under the LGPL. It was built to help Red Hat Enterprise Linux and its derivatives meet US federal certification requirements, and that origin still shows in where it's used hardest today — RHEL, its downstream and upstream relatives, and any Linux estate that has to prove compliance against a government or industry-published benchmark rather than a benchmark it wrote itself.
The problem OpenSCAP solves is specifically about the source of a control, not the mechanics of running one. A regulated organization doesn't usually get to decide what "secure configuration" means for itself — a benchmark author (DISA, CIS, a sector regulator) already decided, publishes the decision as a versioned document, and expects an assessor to be able to verify a system against that exact document, not against someone's paraphrase of it. Hand-translating a four-hundred-rule benchmark into a testing framework's own DSL is real, repeated, error-prone work, and it has to be redone every time the benchmark itself is revised. OpenSCAP's answer is to make the benchmark itself the executable artifact: download the content the standards body already published, point oscap at it, and run it — with zero controls hand-authored, and zero ambiguity about whether the check you ran matches the check the publisher intended.
OpenSCAP's value isn't that it checks a config file faster than a hand-written script could — plenty of tools do that. Its value is that the check you run is the standards body's own artifact, byte for byte, not your team's interpretation of it. That distinction barely matters for an internal style guide. It matters enormously the moment an external assessor — a DISA reviewer, a FedRAMP 3PAO, a PCI QSA — is the one deciding whether your evidence is credible.
Architecture: SCAP's component standards, one datastream, one engine
☺ Like you're 10: One file bundles the table of contents, the actual test instructions, and a way to check the instructions apply to your exact machine — and one program reads that whole file and runs it.
SCAP isn't one format — it's several component specifications that work together, and knowing which piece does which job is most of understanding how oscap behaves.
| Standard | What it actually is |
|---|---|
| XCCDF (Extensible Configuration Checklist Description Format) | The checklist's structure: profiles (a named, selectable set of rules — "STIG," "CIS Level 1 — Server"), groups, and rules, each rule pointing at the test logic that actually evaluates it. |
| OVAL (Open Vulnerability and Assessment Language) | The machine-testable logic itself: definitions built from tests, objects (what to inspect — a file, a sysctl key, an installed package), and states (the value expected). |
| OCIL (Open Checklist Interactive Language) | Questions for the human, for the slice of a benchmark that can't be automated at all — "confirm the server room door has a functioning lock." oscap surfaces these as manual prompts rather than skipping them silently. |
| CPE (Common Platform Enumeration) | A naming scheme for platforms, used so a scan knows whether a given rule even applies to the system in front of it before running it. |
| CCE (Common Configuration Enumeration) | A unique identifier for a specific configuration issue, cross-referenced from an XCCDF rule so different tools and reports can agree on what a finding actually is. |
| ARF (Asset Reporting Format) | The interchange bundle: a scan's target-system characteristics, XCCDF results, and the underlying OVAL results, packaged into one document — the artifact this course's compliance-as-code chapter feeds into fleet-wide aggregation. |
In practice you almost never touch these as five separate files. Published SCAP content ships as a single source datastream — one XML file bundling an XCCDF benchmark, one or more OVAL definitions files, a CPE dictionary, and often OCIL, all cross-referenced internally. That's the file you actually point oscap at: a RHEL 9 datastream is typically named something like ssg-rhel9-ds.xml, and it's the single artifact that makes OpenSCAP's whole workflow feel like "download one file, run one command" rather than "assemble five specifications yourself."
Two details in that diagram matter more than the boxes themselves. First, oscap evaluates entirely locally: the datastream and the target being scanned are both on the same machine (or reachable via a wrapper, covered below), so a scan produces no external network call and no data leaves the host being assessed — a property regulated environments care about as much as the check content itself. Second, the CPE applicability check runs before a rule is ever evaluated: a rule for "Apache configuration" simply doesn't run — and correctly reports notapplicable rather than fail — on a host with no Apache package installed. That's a feature until the CPE dictionary itself is wrong about what's installed, which is one of this page's gotchas below.
Where the content comes from: the SCAP Security Guide and the DISA STIG connection
☺ Like you're 10: You almost never write the checklist yourself. Either a community project maintains it for a general standard, or the government publishes its own official version for its own systems — and OpenSCAP runs either one exactly as written.
The SCAP Security Guide project (developed today under the name ComplianceAsCode/content, though the RHEL package that ships it is still called scap-security-guide) is the community source most teams reach for first: security-hardening content and SCAP datastreams for RHEL and its relatives, Ubuntu, Debian, several other distributions, and a growing set of application-layer targets, built and reviewed in the open on GitHub. It bundles multiple profiles per product — a CIS Level 1 profile, a CIS Level 2 profile, and, for the products DISA has a STIG for, a STIG-aligned profile derived from DISA's own published requirements.
The other content source is the one that gives this page its regulated-environment payoff: DISA — the Defense Information Systems Agency — publishes Security Technical Implementation Guides (STIGs) directly, product by product, on a roughly quarterly release cadence, versioned as, for example, "V1R2" (Version 1, Release 2). Each STIG derives from a more generic parent document, a Security Requirements Guide (SRG), adapted to one specific product. For many products DISA publishes machine-readable SCAP content alongside the STIG itself — the same source-datastream format described above — distributed through the DoD Cyber Exchange. Not every STIG requirement has an automated OVAL check behind it: physical-security and procedural rules stay manual, evaluated through OCIL prompts or worked by hand in DISA's own STIG Viewer GUI against a .ckl checklist file — a complementary tool to oscap, not a replacement for it, since it covers exactly the portion a SCAP scan structurally can't automate.
The detail worth knowing precisely, because it's the whole reason this page exists next to InSpec's: every automatable STIG rule carries a Control Correlation Identifier (CCI) — an ID like CCI-000366 — and DISA maintains the mapping from every CCI to the NIST SP 800-53 control it implements (a rule enforcing a specific SSH setting, for instance, commonly traces to 800-53's CM-6, Configuration Settings). That chain — rule → CCI → 800-53 control — is what a DoD system's Risk Management Framework (RMF) authorization package is actually built from, and it's published by DISA, inside the content itself, not invented by whichever engineer configured the scan. Run the official DISA datastream and that traceability comes for free. Some products also have a DISA STIG with no matching automated content yet, or content that lags a STIG's own release by a cycle — check the DoD Cyber Exchange's published SCAP content list for the product you need before assuming a fully automated STIG scan exists for it.
The community SCAP Security Guide includes a STIG-aligned profile for popular products, and it's usually close to DISA's own published benchmark — but "close" and "identical" aren't the same thing to an assessor. The two projects can diverge slightly in interpretation or release timing. When evidence is headed to a DISA reviewer, a Certification Authority, or an Authorizing Official who expects DISA's own artifact specifically, scan with the datastream DISA actually published, downloaded from the DoD Cyber Exchange, not a community approximation of it — confirm the current download location and file naming on DISA's own site, since it has moved before.
The content you actually work with: profiles, rules, and tailoring
☺ Like you're 10: You mostly pick which named checklist to run, not write a new one — and when one item on the checklist really shouldn't apply to your machine, you write down why in its own separate file instead of editing the original.
Inside a datastream, a rule and the OVAL definition it points to look like this — shown once, because it's worth seeing the machinery, even though the point of this whole page is that you rarely author it:
<!-- inside the XCCDF benchmark: a rule referencing an OVAL definition and its CCI -->
<xccdf:Rule id="xccdf_org.ssgproject.content_rule_sshd_disable_root_login" severity="high">
<xccdf:title>Disable SSH Root Login</xccdf:title>
<xccdf:reference href="https://public.cyber.mil/stigs/cci/">CCI-000366</xccdf:reference>
<xccdf:ident system="https://nvd.nist.gov/800-53">CM-6</xccdf:ident>
<xccdf:check system="http://oval.mitre.org/XMLSchema/oval-definitions-5">
<xccdf:check-content-ref href="ssg-rhel9-oval.xml"
name="oval:ssg-sshd_disable_root_login:def:1"/>
</xccdf:check>
</xccdf:Rule>
<!-- inside the OVAL definitions file: the actual test the rule above points to -->
<definition id="oval:ssg-sshd_disable_root_login:def:1" class="compliance">
<criteria>
<criterion test_ref="oval:ssg-test_sshd_disable_root_login:tst:1"/>
</criteria>
</definition>
<!-- the test_ref points to an OVAL object (the sshd_config file, the PermitRootLogin key)
and an OVAL state (the expected value "no") — the actual comparison logic -->
That <xccdf:reference> and <xccdf:ident> pair is the regulatory traceability described above, expressed as literal XML — it exists in the content whether or not you ever look at it, and it's what an evidence bundle carries forward without a human having typed it.
What you actually work with day to day is a level above this: a profile (a named subset of rules — xccdf_org.ssgproject.content_profile_stig, or a CIS-level equivalent), selected with one flag, and a tailoring file for the cases where a profile is almost, but not exactly, right for a given host. A tailoring file is its own XCCDF document layered on top of the base datastream — it selects or deselects specific rules, and optionally overrides an OVAL variable's expected value — authored either by hand, through the SCAP Workbench GUI, or generated at the CLI with autotailor, a tool that ships alongside the SCAP Security Guide project specifically so a tailoring file doesn't require opening a GUI at all:
$ autotailor \
--profile-id xccdf_org.ssgproject.content_profile_stig \
--new-profile-id stig_bastion_customized \
--select xccdf_org.ssgproject.content_rule_sshd_disable_root_login=false \
--output tailoring.xml \
ssg-rhel9-ds.xml
# flag names have shifted slightly across autotailor releases — check `autotailor --help`
# against the version actually on the machine before assuming an example matches exactlyBecause a tailoring file is its own committed artifact — checked into the same control repo as the datastream it modifies, exactly like compliance as code at scale covers for a fleet-wide evidence pipeline — an exception is exactly as auditable as the profile it changes, not a one-off flag typed on a command line and forgotten.
Day-to-day commands
☺ Like you're 10: One command to see what's on the checklist, one to actually run it, and a handful of wrapper commands for checking a machine you can't or shouldn't log into directly.
# list every profile a datastream actually contains — run this before trusting any ID from a tutorial
$ oscap info ssg-rhel9-ds.xml
Profiles:
Title: DISA STIG for Red Hat Enterprise Linux 9
Id: xccdf_org.ssgproject.content_profile_stig
Title: CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server
Id: xccdf_org.ssgproject.content_profile_cis_level1_server
# validate a datastream's own XML against its schema before trusting it in a pipeline
$ oscap xccdf validate ssg-rhel9-ds.xml
# the core evaluation command: run a profile, write results, write a human-readable report
$ oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_stig \
--results-arf arf-$(hostname).xml \
--report report-$(hostname).html \
ssg-rhel9-ds.xml
# apply a tailoring file on top of the base profile it was built from
$ oscap xccdf eval \
--tailoring-file tailoring.xml \
--profile stig_bastion_customized \
--results-arf arf-bastion.xml \
ssg-rhel9-ds.xml
# generate an Ansible remediation playbook for every FAILING rule in a profile — review before running it
$ oscap xccdf generate fix \
--profile xccdf_org.ssgproject.content_profile_stig \
--fix-type ansible \
ssg-rhel9-ds.xml > remediation.yml
# scan a stopped VM disk image directly, offline, without booting it — a golden-image gate
$ oscap-vm domain web-golden-image xccdf eval \
--profile xccdf_org.ssgproject.content_profile_stig \
--results results.xml ssg-rhel9-ds.xml
# scan a container image the same way, before it's ever run
$ oscap-podman registry.acme.io/checkout:1.8.2 xccdf eval \
--profile xccdf_org.ssgproject.content_profile_stig \
--results results.xml ssg-rhel9-ds.xml
# scan a remote host over SSH with no openscap install required on the target at all
$ oscap-ssh ops@10.0.4.21 22 xccdf eval \
--profile xccdf_org.ssgproject.content_profile_stig \
--results results.xml ssg-rhel9-ds.xmlThat family of wrapper scripts — oscap-vm, oscap-podman, oscap-ssh, and oscap-chroot for a mounted filesystem — is what turns OpenSCAP from "something you run on a host you're logged into" into something you can wire into a pipeline: scan the golden image before it's ever booted, the container before it's ever pushed, or a fleet of remote hosts without installing openscap-scanner on every single one of them.
Gotchas and failure modes
☺ Like you're 10: Most surprises come from one of two places — the checklist decided a rule doesn't apply to you when it actually does, or a fix script ran on its own and broke the exact thing it was protecting.
- A CPE mismatch can produce a suspiciously clean scan. If a datastream's CPE dictionary doesn't recognize the exact OS release running — a minor point release it wasn't updated for, a minimal container base image missing the release files CPE checks for — rules can silently resolve to
notapplicableacross the board instead offail. A profile summary showing "0 failures" is not automatically good news; check the pass/notapplicable/error breakdown, not just the failure count, before reporting a scan as clean. --remediateor a generated fix playbook is still a live change to the system, authored by DISA, not by your team. The Bash and Ansible remediation content bundled in a datastream is written to satisfy the rule generically, and STIG remediation in particular can be aggressive — disabling a service, locking a PAM setting, blocking USB mass storage — in ways that can break something your specific host actually depends on. Treat generated remediation with the exact same converge-then-verify discipline as any other hardening change before trusting it against production; never point it at a fleet you haven't tested it against first.- Not every result is a pass or a fail.
notcheckedandnotselectedmean a rule exists in the benchmark but has no automated test, or wasn't part of the chosen profile — some STIG requirements are procedural and stay in STIG Viewer's manual checklist by design. Reading a SCAP-only scan as the complete audit, when the STIG it's drawn from includes manual-only rules, overstates what the automated portion actually covers. - Rule and profile IDs shift between benchmark releases. A DISA STIG's quarterly release can renumber rule IDs or add new ones; a tailoring file or waiver list written against an old release may silently stop covering what it used to. Re-run
oscap infoagainst a freshly downloaded datastream before assuming an ID from last quarter — or from training material — still resolves. - Community content and the official DISA benchmark can disagree. Covered above, and worth repeating here as an operational habit: know which content source generated the evidence you're about to hand to an assessor, and confirm that's the one they expect.
A full STIG or CIS Level 2 profile can run several hundred rules, each backed by its own OVAL evaluation — on an older or resource-constrained host that's real CPU time and a multi-megabyte ARF result file, not an instant check. Budget for it in a pipeline stage's timeout the same way you would for a large test suite, and don't assume a scan that's still running after a minute has hung.
OpenSCAP vs. InSpec: where the standards-based approach pays off
☺ Like you're 10: InSpec is amazing at checking anything you can describe — but you have to describe it. OpenSCAP already comes with the description written, government-published, and pre-linked to exactly which regulation it satisfies.
This course's own compliance-as-code chapter runs InSpec and OpenSCAP side by side for a reason: they solve the same job — proving a control holds, without changing anything — through genuinely different mechanisms, and each mechanism has a place the other one reaches less naturally.
| InSpec | OpenSCAP | |
|---|---|---|
| Content model | A control is Ruby, in a small DSL — arbitrary code, ultimately | A rule is declarative XCCDF pointing at declarative OVAL — data, not code |
| Where content comes from | You write it, or adopt a community-maintained profile (e.g. MITRE's derived STIG baselines) that a third party has translated from the source benchmark | You run the benchmark author's own published artifact directly — no translation layer between the standard and the check |
| Regulatory traceability | Exists only if a human adds it, as a tag — nothing forces a control to declare its 800-53 lineage | Baked into the content itself as <xccdf:reference>/<xccdf:ident> elements — CCI and 800-53 mapping ship with the rule |
| Extending to a novel check | Write a new Ruby resource — checks anything you can script, including cloud-API resources with no host-level equivalent at all | Bounded by OVAL's fixed object vocabulary for that SCAP version; a genuinely new kind of check may need OCIL's manual prompt instead |
| Built-in remediation | None — InSpec only ever reads state; fixing is a separate Ansible/Chef role you write | Bundled Bash/Ansible fix content ships in the same datastream, generated with oscap xccdf generate fix |
| Best fit | Org-specific controls, cloud-resource checks, anything not already codified in a published benchmark | Any environment that has to prove compliance against a specific government or industry-published benchmark, especially DISA STIGs |
The sharpest way to see the difference: standing up STIG evidence for a new RHEL fleet in OpenSCAP is oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_stig against DISA's own datastream — zero controls hand-authored, the same artifact DISA itself expects an assessor to trust. Doing the equivalent in InSpec means either adopting a community-maintained translation of that same STIG (MITRE publishes InSpec "stig-baseline" profiles for several products, tracked as a derived, secondary artifact a team still has to keep in sync with DISA's own quarterly releases) or hand-writing several hundred controls and personally owning the risk that your reading of DISA's prose matches DISA's actual intent. That's not a knock on InSpec's design — it's simply not the job InSpec's Ruby DSL was built to solve on its own; for a check that has no government-published equivalent at all, InSpec's arbitrary-code flexibility is the tool with no ceiling, and OpenSCAP has nothing to offer because there's no benchmark to run in the first place.
In practice, most fleets that use both run OpenSCAP against the officially published benchmark for whatever regulatory or industry standard actually governs them, and InSpec for the org-specific controls layered on top — the DevSec Hardening Framework's community baselines, plus whatever a platform team decided to check that nobody outside the company would ever publish a standard for. See compliance as code at scale for exactly how both tools' evidence gets normalized through the SAF CLI into one Heimdall dashboard once a fleet runs a mix of the two, and compliance & governance for the frameworks (SOC 2, ISO 27001, PCI-DSS, HIPAA, GDPR) this evidence typically gets mapped against outside a DoD-specific RMF package. Readers pursuing a credential built specifically around this skill set should look at GIAC's Cloud Security Automation (GCSA) exam, which covers compliance-as-code tooling including SCAP-based scanning directly — confirm current objectives and pricing on GIAC's own page before enrolling, the way this course flags for every certification it names.
Nutty: Downloaded this quarter's DISA STIG for RHEL 9, ran oscap xccdf eval against the golden image before it ever booted. Three hundred and eleven rules, evidence filed, done in under a minute of my time.
Timmy: Three hundred eleven controls and you didn't write a single one? I don't trust a checklist I didn't watch get built.
Nutty: I didn't build it — DISA did, and published it. Every rule already carries its own CCI, mapped straight to a NIST 800-53 control. If an assessor asks why a rule exists, the content answers on DISA's authority, not mine.
Recon: That's the same trade I make with a Terraform policy versus writing bespoke IAM logic by hand — except your checklist didn't even come from inside the company.
Foxy: What happens the moment you need to check something DISA never wrote a rule for — some internal thing only we care about?
Nutty: Then it's not OpenSCAP's job anymore. That's exactly when I hand it to InSpec — arbitrary Ruby, checks anything I can describe. OpenSCAP for what's already standardized, InSpec for what isn't.
Sol: And to be clear — this is your host's own files and packages. My cloud accounts don't have an sshd_config to check. Different layer entirely.
1. Name SCAP's core component standards and what each one actually defines. 2. What is a "source datastream," and why does bundling several SCAP components into one file matter operationally? 3. What exactly does a CCI trace, and why does that mapping living inside the content itself matter more in a regulated environment than it would for an internal style guide? 4. A profile scan comes back with zero failures. What's the one thing you should check before reporting the host as compliant? 5. Give one concrete reason a fleet would run both OpenSCAP and InSpec rather than picking exactly one.
Check your answers
- XCCDF defines the checklist structure — profiles, groups, and which rules belong to a named benchmark. OVAL defines the actual testable logic (definitions, tests, objects, states) a rule points to. OCIL defines manual, human-answered questions for checks that can't be automated. CPE names platforms so a scan can tell whether a rule even applies. CCE gives a configuration issue a stable, tool-independent identifier. ARF bundles a scan's target info, XCCDF results, and OVAL results into one interchange document.
- A source datastream is one XML file bundling an XCCDF benchmark, its OVAL definitions, and a CPE dictionary together, cross-referenced internally. It matters operationally because it turns "assemble five separate specifications correctly" into "download one file and point
oscapat it" — the packaging is what makes the workflow simple, not the underlying standards themselves. - A CCI (Control Correlation Identifier) traces a specific rule to the NIST SP 800-53 control it implements. That mapping matters more in a regulated environment because an authorization package (an ATO under RMF, for example) is built directly from that traceability chain, and having it published by the standards body itself — rather than added after the fact by whoever wrote the check — is what makes the evidence credible to an assessor who didn't write it.
- Whether the "zero failures" is because everything genuinely passed, or because a CPE mismatch caused rules to resolve as
notapplicableinstead of being evaluated at all. Check the full pass/notapplicable/notchecked/error breakdown, not just the failure count, before trusting a clean result. - Any reasonable answer citing the two tools' different strengths: for example, running OpenSCAP against an officially published benchmark (a DISA STIG, a CIS profile) for guaranteed regulatory traceability, while using InSpec for org-specific controls or cloud-resource checks that have no published SCAP content at all — OpenSCAP has nothing to run when no benchmark exists for a given check.