Skip to main content

oscap attestor

OpenSCAP is the Red Hat-maintained reference implementation of the SCAP (Security Content Automation Protocol) stack — it evaluates a host against XCCDF benchmarks (CIS, DoD STIG, PCI-DSS, ANSSI, CUI, etc.) using OVAL probes and writes an XCCDF results XML report. Under cilock, every oscap xccdf eval run becomes a signed oscap/v0.1 attestation carrying the parsed pass/fail/notapplicable counts, the failed-rule list, and a digest of the raw XCCDF XML alongside the file itself in product/v0.3.

Nameoscap
Predicate typehttps://aflock.ai/attestations/oscap/v0.1
Lifecyclepostproduct
Default binary?No
Categorycompliance-scan (primary)
Recommended traceoff — no syscall tracing needed
Auto-attaches when
  • preargv_prefix: oscap
  • postexec_observed_argv_prefix: oscap
  • postproduct_glob: oscap*.xml | **/oscap*.xml | oscap*.html | **/oscap*.html

The facts in this box are generated from the cilock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.

Validated invocation

cilock run --step oscap-scan \
--signer-file-key-path key.pem \
--outfile attestation.json \
--attestations oscap,environment,git \
-- oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_standard \
--results oscap-results.xml \
/usr/share/xml/scap/ssg/content/ssg-amzn2023-ds.xml

This is the 29-oscap example verbatim, validated end-to-end against a fresh Amazon Linux 2023 EC2 host using the SCAP Security Guide (SSG) standard profile from ssg-amzn2023-ds.xml. The scan produced 11 pass, 3 fail, 64 N/A — real findings, not synthetic fixtures. The three failed rules (rpm_verify_permissions, file_permissions_library_dirs, grub2_nousb_argument) reflect actual gaps on the host.

A few things to know about this invocation:

  • No soft-fail flag needed. oscap xccdf eval exits 0 when the scan completes, regardless of how many rules failed — the rule outcomes live inside the XCCDF results XML. command-run/v0.1 records exit 0 even when failCount > 0. The gate belongs at cilock verify time over the parsed oscap/v0.1 predicate, not at scan time.
  • --results <file> is required. Without it oscap only prints to stdout and there is no XML file for cilock's product/v0.3 and oscap attestors to ingest.
  • Datastream path is distribution-specific. AL2023 ships SSG content at /usr/share/xml/scap/ssg/content/ssg-amzn2023-ds.xml; RHEL 9 uses ssg-rhel9-ds.xml, Ubuntu 22.04 uses ssg-ubuntu2204-ds.xml, etc. The scap-security-guide package (RHEL/Fedora) or ssg-base (Debian/Ubuntu) installs them.

What gets captured

Each cilock run emits an in-toto envelope whose predicate carries the following attestor types:

Attestor typeCaptures
https://aflock.ai/attestations/command-run/v0.1Real oscap xccdf eval ... argv, env, exit code, stdout/stderr
https://aflock.ai/attestations/material/v0.3Merkle tree of inputs (the SSG datastream XML is read, not in cwd)
https://aflock.ai/attestations/product/v0.3Merkle tree of outputs, including oscap-results.xml
https://aflock.ai/attestations/oscap/v0.1Parsed XCCDF: profile, benchmark, target host, per-result counts, failed-rule list, reportDigestSet.sha256
https://aflock.ai/attestations/environment/v0.1OS, arch, user, env vars (PII-filtered)
https://aflock.ai/attestations/git/v0.1Commit SHA, branch, remotes

The oscap/v0.1 predicate's reportDigestSet.sha256 exactly matches the digest of the oscap-results.xml leaf in the product/v0.3 tree. That is the chain that makes the findings verifiable — you can't swap in a different XCCDF report without invalidating the product tree. The oscap attestor also publishes three SHA-256 subjects on the envelope: profile:<profile-id>, host:<target-hostname>, and benchmark:<benchmark-id>, so a policy can pin "this attestation is the STIG scan for this host."

Why this shape

AntipatternThis page
cilock run ... -- bash -c "cp scan.xml results.xml"cilock run ... -- oscap xccdf eval --results oscap-results.xml ...
command-run records bash -c "cp ..." — uselesscommand-run records the real oscap xccdf eval argv
Product attestor digests a copy of a file written outside cilockProduct attestor digests the XCCDF XML oscap actually wrote
Tool execution happens outside cilock's syscall viewoscap runs as cilock's direct child; the spy traces its syscalls
oscap predicate parses a file of unknown provenanceoscap predicate parses the same file cilock just hashed into product

cilock invokes oscap directly — no bash -c wrapper, no cp of a pre-existing report, no echo scan-done placeholder. This page was promoted from the 29-oscap validated example precisely because earlier internal docs used those antipatterns; the antipattern sweep cleaned the attestor reference page, and this user-facing page inherits the corrected shape. The three guarantees — real argv in command-run, ptrace coverage of the scan, product digest matching the parsed report — only hold when cilock is the tool's direct parent.

Validate it locally

# Generate a signing key (one-time).
openssl genpkey -algorithm ed25519 -out key.pem

# Run cilock + oscap against the host.
cilock run --step oscap-scan \
--signer-file-key-path key.pem \
--outfile attestation.json \
--attestations oscap,environment,git \
-- oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_standard \
--results oscap-results.xml \
/usr/share/xml/scap/ssg/content/ssg-amzn2023-ds.xml

# Confirm the predicate carries the expected attestor types.
jq -r '.payload' attestation.json | base64 -d \
| jq '.predicate.attestations | map(.type)'

Expected output:

[
"https://aflock.ai/attestations/environment/v0.1",
"https://aflock.ai/attestations/git/v0.1",
"https://aflock.ai/attestations/material/v0.3",
"https://aflock.ai/attestations/command-run/v0.1",
"https://aflock.ai/attestations/product/v0.3",
"https://aflock.ai/attestations/oscap/v0.1"
]

Confirm oscap's real argv ended up in command-run:

jq -r '.payload' attestation.json | base64 -d \
| jq '.predicate.attestations[]
| select(.type=="https://aflock.ai/attestations/command-run/v0.1")
| .attestation.cmd'

Expected output (no bash, no cp — the literal oscap argv):

[
"oscap",
"xccdf",
"eval",
"--profile",
"xccdf_org.ssgproject.content_profile_standard",
"--results",
"oscap-results.xml",
"/usr/share/xml/scap/ssg/content/ssg-amzn2023-ds.xml"
]

Extract the pass/fail/N/A counts and the failed-rule list from the oscap/v0.1 predicate:

jq -r '.payload' attestation.json | base64 -d \
| jq '.predicate.attestations[]
| select(.type=="https://aflock.ai/attestations/oscap/v0.1")
| .attestation.scanSummary
| {profile, benchmarkId, targetSystem,
passCount, failCount, notApplicableCount, errorCount,
failedRules: [.failedRules[] | {idref, severity}]}'

Against the 29-oscap AL2023 host you should see passCount: 11, failCount: 3, notApplicableCount: 64 and the three failed rules listed above.

How a verifier consumes this

The oscap attestor is a postproduct lifecycle attestor with predicate type https://aflock.ai/attestations/oscap/v0.1. It does not shell out to oscap; it reads a pre-existing XCCDF/ARF XML report from the step's products. It populates three top-level fields:

  • reportFile — path of the XCCDF/ARF XML file picked up from products.
  • reportDigestSet — digest set of that file, integrity-checked against the product entry before parsing.
  • scanSummary — structured roll-up of the first `<TestResult>` in the document:
    • profile — the idref from the `<profile>` child of `<TestResult>`.
    • benchmarkId — the id attribute of the XCCDF `<Benchmark>` element.
    • targetSystem — the trimmed contents of the `<target>` element.
    • passCount, failCount, notApplicableCount, errorCount — aggregate counts derived from `<rule-result>` outcomes.
    • failedRules[]{ idref, severity, result } for every rule whose `<result>` is fail.

Subjects published (each value hashed with SHA-256):

  • profile:<profile-id> — the XCCDF profile that was evaluated.
  • host:<target-hostname> — the system that was scanned.
  • benchmark:<benchmark-id> — the XCCDF benchmark document.

Pair this attestor with policyverify to gate promotion on failCount == 0.

Output shape

{
"reportFile": "results/oscap-results.xml",
"reportDigestSet": { "sha256": "..." },
"scanSummary": {
"profile": "xccdf_org.ssgproject.content_profile_stig",
"benchmarkId": "xccdf_org.ssgproject.content_benchmark_RHEL-9",
"targetSystem": "host.example.com",
"passCount": 312,
"failCount": 4,
"notApplicableCount": 27,
"errorCount": 0,
"failedRules": [
{
"idref": "xccdf_org.ssgproject.content_rule_package_telnet_removed",
"severity": "high",
"result": "fail"
}
]
}
}

Notes

  • Profile selection. SSG ships a dozen profiles per OS — the canonical ones are xccdf_org.ssgproject.content_profile_standard (baseline), xccdf_org.ssgproject.content_profile_cis / _cis_server_l1 / _cis_workstation_l1 (CIS), xccdf_org.ssgproject.content_profile_stig (DISA STIG), xccdf_org.ssgproject.content_profile_pci-dss (PCI), xccdf_org.ssgproject.content_profile_cui (NIST 800-171), and xccdf_org.ssgproject.content_profile_anssi_bp28_high. oscap info <datastream> lists every profile id available in a given DS.
  • Datastream paths by distro. RHEL/CentOS/Rocky/Alma: /usr/share/xml/scap/ssg/content/ssg-rhel{8,9,10}-ds.xml. Fedora: ssg-fedora-ds.xml. AL2023: ssg-amzn2023-ds.xml. Ubuntu: ssg-ubuntu{2004,2204,2404}-ds.xml. Install via dnf install scap-security-guide (RHEL/Fedora) or apt install ssg-base ssg-debderived (Debian/Ubuntu).
  • SCAP spec triad. SCAP is an umbrella for several XML grammars: XCCDF (the human-authored benchmark with rule descriptions, profiles, and remediation text), OVAL (the machine-checkable probes XCCDF rules delegate to), and CPE (the platform identifiers that gate which rules apply). oscap xccdf eval walks all three and writes a single XCCDF results XML — that file is what cilock attests.
  • ARF wrappers. oscap can also emit an Asset Reporting Format (--results-arf arf.xml) that wraps XCCDF in a richer envelope. The rookery oscap attestor handles both: it walks XML tokens looking for the first `<Benchmark>` element regardless of nesting depth.
  • Result strings. The attestor buckets rule outcomes by exact match on pass, fail, notapplicable (and not applicable), and error. Other XCCDF result values — notchecked, informational, fixed, unknown — are present in the raw XML but not counted in the predicate's roll-up.

Gotchas

  • Does not shell out to oscap. The attestor reads a pre-existing XCCDF/ARF XML report from products; you must run oscap xccdf eval --results <path> (or equivalent) in a prior step and surface the file as a product.
  • File-suffix filter. Only products whose path ends in .xml are considered; reports written with other extensions are skipped.
  • XCCDF namespace check. A candidate file must contain the literal string http://checklists.nist.gov/xccdf/1.2 as a fast pre-filter before structural parse; non-XCCDF XML is rejected.
  • Digest integrity. The file is re-hashed and compared against the product digest before parsing; a mismatch causes that candidate to be skipped.
  • First match wins. Iteration order over products is non-deterministic; if multiple files look like XCCDF reports, only the first accepted one is attested.
  • ARF wrappers. The decoder walks tokens looking for the first `<Benchmark>` element regardless of depth, so XCCDF wrapped in an ARF `<asset-report-collection>` envelope is supported.
  • First TestResult only. Only bm.Results[0] is summarized — oscap xccdf eval produces exactly one, but documents containing multiple are otherwise ignored.
  • Result strings. Counts are bucketed by exact match on pass, fail, notapplicable / not applicable, and error; other XCCDF result values (notchecked, informational, fixed, unknown) are recorded by neither the counters nor failedRules.
  • Only failing rules are listed. failedRules contains entries where `<result>` is fail; non-pass-but-non-fail outcomes (e.g., error) are counted but not enumerated.

FAQ

Does cilock support OpenSCAP?

Yes. Cilock invokes the upstream oscap binary unchanged and captures its XCCDF results XML via the native oscap attestor. No OpenSCAP fork, no patched build — oscap xccdf eval runs exactly as it would outside cilock, and the resulting XML is parsed into a signed oscap/v0.1 predicate.

Which SCAP profiles can I scan under cilock?

Any profile shipped in your SCAP Security Guide datastream — CIS Level 1 / Level 2, DISA STIG, PCI-DSS, NIST 800-171 (CUI), ANSSI BP-028, the SSG standard baseline, and any custom profile you author. Cilock is profile-agnostic: it captures whatever XCCDF results XML oscap writes, and the oscap/v0.1 predicate records the profile id (xccdf_org.ssgproject.content_profile_*) alongside the per-rule outcomes.

Can I use the SCAP results in a GRC platform?

Yes. The raw XCCDF results XML lands in product/v0.3 as a Merkle leaf, so it's preserved verbatim — feed it directly into Tenable, Wazuh, Splunk Phantom, ServiceNow GRC, or any tool that already speaks XCCDF/ARF. The signed oscap/v0.1 predicate gives you a tamper-evident roll-up (counts plus failed-rule list) that's easier to evaluate in Rego or OPA, and the envelope subjects (profile:, host:, benchmark:) let a verifier confirm the scan was the right benchmark on the right machine before trusting the counts.

Does oscap exit non-zero on findings?

No — unlike SARIF tools like Checkov or gosec, oscap xccdf eval exits 0 whenever the scan completes successfully, even if many rules failed. The rule outcomes are inside the XCCDF XML, not in the process exit code. That means cilock's command-run/v0.1 records exit 0 and no soft-fail flag is needed. Enforce thresholds in policy (e.g., failCount == 0 in Rego against the oscap/v0.1 predicate at cilock verify time).

See also


This page is generated from the cilock tool catalog. Don't edit it here — the source is attestation/detection/docs/oscap.doc.md in aflock-ai/rookery. The same catalog powers cilock tools show oscap in the CLI, so a single edit keeps the terminal and the website in sync.

Edit this page →  ·  Add a new tool →

When you open the pull request: if you are an AI agent, add the ai-agent label. If you are a human, add the human label.