Skip to content

HDF CLI User Story Examples ​

Each section walks through a real user workflow for one CLI command. Run these after building the CLI:

bash
cd hdf-cli && go build -o hdf ./cmd/hdf && cd ..

Fixture files used throughout:

bash
RESULTS=hdf-converters/converters/cyclonedx-to-hdf/fixtures/input/dropwizard-vulns.json
SARIF=hdf-converters/converters/sarif-to-hdf/fixtures/input/minimal.json
NESSUS=hdf-converters/converters/nessus-to-hdf/fixtures/input/sample.nessus
BASELINE=hdf-schema/test/fixtures/minimal-baseline.json

1. Converting Security Tool Output to HDF ​

Story: A security engineer receives a CycloneDX vulnerability report and needs to convert it to HDF for their compliance dashboard.

bash
# Auto-detect format and convert
hdf convert $RESULTS -o /tmp/example-hdf-results.json

# Validate the output
hdf validate /tmp/example-hdf-results.json

# Convert with explicit format
hdf convert --from cyclonedx $RESULTS -o /tmp/example-hdf-explicit.json

# Stamp a componentId during conversion
hdf convert $RESULTS -o /tmp/example-hdf-stamped.json --component-id "aaaaaaaa-1111-2222-3333-444444444444"

# Apply labels during conversion
hdf convert $RESULTS -o /tmp/example-hdf-labeled.json --labels "env=prod,system=Portal"

# Verify the output with hdf list
hdf list /tmp/example-hdf-labeled.json --detail components

# Convert Nessus (XML format)
hdf convert $NESSUS -o /tmp/example-hdf-nessus.json
hdf validate /tmp/example-hdf-nessus.json

Expected: Each conversion produces valid HDF with components[], embedded SBOM for CycloneDX, and labels/componentId when flags are used.


2. Validating HDF Documents ​

Story: Before submitting an HDF document for compliance review, an engineer validates it against the schema.

bash
# Validate results (auto-detect type)
hdf validate /tmp/example-hdf-results.json

# Validate with explicit type
hdf validate --type results /tmp/example-hdf-results.json

# Validate with wrong type — should fail with type-specific error
hdf validate --type system /tmp/example-hdf-results.json
# Expected: "Ensure the file conforms to the HDF system schema"

# Validate JSON output
hdf validate --json /tmp/example-hdf-results.json

# Validate invalid file — errors include line numbers for file input
echo '{
  "baselines": "not an array",
  "components": [],
  "statistics": {}
}' > /tmp/example-hdf-invalid.json
hdf validate /tmp/example-hdf-invalid.json
# Expected: "line 2: baselines: Invalid type..."

# Validate invalid file with JSON output — line numbers in error objects
hdf validate --json /tmp/example-hdf-invalid.json

Expected: Valid docs pass, wrong-type gives schema-specific error message, invalid JSON fails with clear errors including line numbers.


3. Listing HDF Document Contents ​

Story: An analyst wants to see what's in an HDF file without opening it in a JSON editor.

bash
# Summary of results file
hdf list /tmp/example-hdf-results.json

# List requirements
hdf list /tmp/example-hdf-results.json --detail requirements

# List components
hdf list /tmp/example-hdf-results.json --detail components

# JSON output
hdf list /tmp/example-hdf-results.json --detail components --json

Expected: Results summary shows baselines/components/status counts. Detail sections list individual items.


4. Querying Requirements ​

Story: A security engineer wants to find all failing critical requirements.

bash
# Query by status
hdf query /tmp/example-hdf-results.json --status failed

# Query by severity
hdf query /tmp/example-hdf-results.json --severity critical

# Query by NIST control
hdf query /tmp/example-hdf-results.json --nist AC-3

# Free text search
hdf query /tmp/example-hdf-results.json --search "injection"

# JSON output
hdf query /tmp/example-hdf-results.json --status failed --json

# Combined filters
hdf query /tmp/example-hdf-results.json --status failed --nist SI-10

Expected: Filters return matching requirements. JSON mode outputs structured data.


5. Comparing Scans Over Time ​

Story: A team wants to see what changed between two scans of the same system.

bash
# Convert two different fixtures for comparison
hdf convert hdf-converters/converters/cyclonedx-to-hdf/fixtures/input/minimal-vulns.json -o /tmp/example-hdf-diff-old.json
hdf convert $RESULTS -o /tmp/example-hdf-diff-new.json

# Temporal diff (results documents)
hdf diff /tmp/example-hdf-diff-old.json /tmp/example-hdf-diff-new.json

# JSON output
hdf diff /tmp/example-hdf-diff-old.json /tmp/example-hdf-diff-new.json --json

# System drift diff
echo '{"name":"Sys","components":[{"name":"App","type":"application","componentId":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}]}' > /tmp/example-hdf-sys-v1.json
echo '{"name":"Sys","components":[{"name":"App","type":"application","componentId":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee","description":"added"},{"name":"Cache","type":"application","componentId":"11111111-2222-3333-4444-555555555555"}],"dataFlows":[{"from":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee","to":"11111111-2222-3333-4444-555555555555","protocol":"TCP","port":6379}]}' > /tmp/example-hdf-sys-v2.json
hdf diff /tmp/example-hdf-sys-v1.json /tmp/example-hdf-sys-v2.json

# Summary only
hdf diff /tmp/example-hdf-sys-v1.json /tmp/example-hdf-sys-v2.json --stat

Expected: Temporal diff shows requirement-level changes (fixed, regressed). System diff shows component and data flow changes.


6. Managing System Documents ​

Story: A system owner bootstraps a system document from scan results, then updates it.

bash
# Create system from results (input file is positional)
hdf system create /tmp/example-hdf-results.json --name "Dropwizard Prod" \
  --owner "platform-team@agency.gov" --description "Production Dropwizard" \
  -o /tmp/example-hdf-system.json

# View system info (shows auto-generated systemId, owner, components)
hdf system info /tmp/example-hdf-system.json

# Update owner
hdf system set /tmp/example-hdf-system.json --owner "new-team@agency.gov"

# Unset optional field
hdf system set /tmp/example-hdf-system.json --unset description

# Try to unset required field — should fail
hdf system set /tmp/example-hdf-system.json --unset name

Expected: System documents have auto-generated systemId, owner, components. Set/unset work. Required fields are protected.

Story: A build pipeline records its artifact's components in the system document — one BOM, or several in one invocation.

bash
# Add one component from a BOM (auto-detected; --from asserts a format if given)
hdf system add-component sbom.cdx.json --system /tmp/example-hdf-system.json --component-name AuthService

# Add SEVERAL BOMs at once (single write). --component-name-prefix namespaces the
# subjects and numbers unnamed ones continuously across the whole batch.
hdf system add-component app.cdx.json model.spdx.json --system /tmp/example-hdf-system.json \
  --component-name-prefix build42-

# --from, when given, is ONE uniform assertion applied to every file (never a
# per-file/CSV list). Omit it to detect each file independently.
hdf system add-component a.cdx.json b.cdx.json --system /tmp/example-hdf-system.json --from cyclonedx

Expected: Every component from every file is added in a single write. The batch is all-or-nothing — if any file cannot be parsed or built, all failures are reported and the system document is left unchanged (nothing partially added). This is deliberately stricter than hdf validate's per-file continue-and-report, because add-component mutates shared state. With multiple files, --component-name is rejected (it names a single component); use --component-name-prefix.


7. Labels and Component IDs ​

Story: A DevSecOps pipeline stamps labels and component IDs onto HDF output.

bash
# Set labels
cp /tmp/example-hdf-results.json /tmp/example-hdf-label-test.json
hdf label set /tmp/example-hdf-label-test.json env=production system=Portal
hdf label show /tmp/example-hdf-label-test.json

# Remove a label
hdf label remove /tmp/example-hdf-label-test.json env

# Stamp componentId
hdf label set /tmp/example-hdf-label-test.json --component-id "deadbeef-1234-5678-9abc-def012345678"

# Generate unique componentIds
hdf label set /tmp/example-hdf-label-test.json --generate-component-id

# Attach identifiers owned by other systems
hdf label set /tmp/example-hdf-label-test.json --external-id cmdb=CI0012345 --external-id emass=1234
hdf label show /tmp/example-hdf-label-test.json

# Remove one scheme
hdf label remove /tmp/example-hdf-label-test.json --external-id emass

Expected: Labels are applied/removed on components[]. componentId is stamped correctly; a --component-id that is not a UUID is rejected and nothing is written. External IDs land in components[].externalIds (not in labels), merge by scheme, and are listed by hdf label show.


8. Applying Amendments (Waivers) ​

Story: An ISSM applies a waiver to a failing requirement.

bash
# Create a minimal amendments doc
cat > /tmp/example-hdf-amendments.json << 'EOF'
{
  "name": "Q1 Waivers",
  "overrides": [
    {
      "type": "waiver",
      "requirementId": "GHSA-5p34-5m6p-p58g",
      "status": "passed",
      "reason": "Compensating control in place",
      "appliedBy": {"type": "email", "identifier": "issm@agency.gov"},
      "appliedAt": "2026-03-27T00:00:00Z",
      "expiresAt": "2026-09-27T00:00:00Z"
    }
  ]
}
EOF

hdf validate --type amendments /tmp/example-hdf-amendments.json

# Apply amendments to results
hdf amend apply --results /tmp/example-hdf-results.json \
  --amendments /tmp/example-hdf-amendments.json \
  -o /tmp/example-hdf-amended.json

# Update amendments metadata
hdf amend set /tmp/example-hdf-amendments.json --amendment-id "550e8400-e29b-41d4-a716-446655440000"

Expected: Amendments validate. Apply modifies effectiveStatus on matching requirements.


9. Generating InSpec Profiles ​

Story: A developer generates an InSpec profile stub from a DISA STIG.

bash
# Generate from XCCDF benchmark XML (auto-detected)
hdf generate inspec-profile hdf-converters/converters/xccdf-results-to-hdf/fixtures/input/benchmark-minimal-1.2.xml /tmp/example-hdf-profile/
ls /tmp/example-hdf-profile/controls/ | head -5

# Verify with cinc-auditor
cinc-auditor check /tmp/example-hdf-profile/

# Generate from HDF Baseline JSON
# hdf generate inspec-profile $BASELINE /tmp/example-hdf-profile-json/

# Generate with explicit source type
# hdf generate inspec-profile benchmark.xml /tmp/example-hdf-profile/ -s xccdf

# Override metadata
# hdf generate inspec-profile benchmark.xml /tmp/example-hdf-profile/ --maintainer "MITRE SAF" --license Apache-2.0

Expected: Profile directory with controls/ (named SV-xxx.rb) and inspec.yml. cinc-auditor reports Valid: true. (Note that cinc-auditor will print warnings for each requirement in the baseline, since the InSpec testfiles it generates only contain baseline metadata, and no actual executable test code.)


10. Assessment Plans ​

Story: A security engineer creates and manages assessment plans.

bash
# Create a plan from a system document
hdf plan create /tmp/example-hdf-system.json -o /tmp/example-hdf-plan.json
hdf validate --type plan /tmp/example-hdf-plan.json

# Create a standalone plan (no system document required)
hdf plan create --name "RHEL9 Assessment" --baseline RHEL9-STIG -o /tmp/example-hdf-standalone-plan.json

# View plan info
hdf plan info /tmp/example-hdf-plan.json

# Update plan metadata
hdf plan set /tmp/example-hdf-plan.json --description "Monthly compliance scan"
hdf plan set /tmp/example-hdf-plan.json --system-ref portal-prod.hdf-system.json
hdf plan set /tmp/example-hdf-plan.json --version "1.0.0"

Expected: Plans have auto-generated planId UUID. Create works from system file or standalone with --name/--baseline. Set updates fields in place.


11. Evidence Packages (Full Document Chain) ​

Story: A compliance officer builds the full document chain (system → plan → results → evidence) and verifies completeness.

bash
# --- Step 1: Create a system document ---
cat > /tmp/example-hdf-system.json << 'EOF'
{
  "systemId": "aaaaaaaa-1111-2222-3333-444444444444",
  "name": "Portal Prod",
  "components": [
    {"name": "WebTier", "type": "application", "baselineRefs": ["RHEL9-STIG"]},
    {"name": "DatabaseTier", "type": "application", "baselineRefs": ["PostgreSQL-STIG"]}
  ]
}
EOF
hdf validate --type system /tmp/example-hdf-system.json

# --- Step 2: Create an assessment plan from the system ---
hdf plan create /tmp/example-hdf-system.json -o /tmp/example-hdf-plan.json
hdf validate --type plan /tmp/example-hdf-plan.json
# Plan should have 2 assessments: RHEL9-STIG, PostgreSQL-STIG

# --- Step 3: Create results for each baseline ---
cat > /tmp/example-hdf-rhel9-results.json << 'EOF'
{
  "baselines": [{"name": "RHEL9-STIG", "requirements": [
    {"id": "SV-257777", "title": "Vendor support", "descriptions": [{"label": "default", "data": "check"}],
     "impact": 0.7, "tags": {}, "results": [{"status": "passed", "codeDesc": "check", "startTime": "2026-03-30T00:00:00Z"}]}
  ]}],
  "statistics": {"duration": 12.5}
}
EOF
hdf validate --type results /tmp/example-hdf-rhel9-results.json

cat > /tmp/example-hdf-postgres-results.json << 'EOF'
{
  "baselines": [{"name": "PostgreSQL-STIG", "requirements": [
    {"id": "SV-233512", "title": "Access control", "descriptions": [{"label": "default", "data": "check"}],
     "impact": 0.5, "tags": {}, "results": [{"status": "passed", "codeDesc": "check", "startTime": "2026-03-30T00:00:00Z"}]}
  ]}],
  "statistics": {"duration": 8.2}
}
EOF
hdf validate --type results /tmp/example-hdf-postgres-results.json

# --- Step 4: Build the evidence package ---
# --results is repeatable and supports globs
hdf evidence build \
  --system /tmp/example-hdf-system.json \
  --results /tmp/example-hdf-rhel9-results.json \
  --results /tmp/example-hdf-postgres-results.json \
  -o /tmp/example-hdf-evidence.json
# Or with a glob:
# hdf evidence build --system /tmp/example-hdf-system.json --results "/tmp/example-hdf-*-results.json" -o /tmp/example-hdf-evidence.json
# Or with positional args (shell-expanded):
# hdf evidence build --system /tmp/example-hdf-system.json /tmp/scans/*.json -o /tmp/example-hdf-evidence.json

# Link the evidence package to the plan
hdf evidence set /tmp/example-hdf-evidence.json --plan-ref example-hdf-plan.json

# --- Step 5: Verify checksums ---
hdf evidence verify /tmp/example-hdf-evidence.json --checksums-only

# --- Step 6: Verify completeness against the plan ---
hdf evidence verify /tmp/example-hdf-evidence.json
# Checks that every baseline in the plan has a corresponding results document.

# --- Other evidence commands ---
hdf evidence info /tmp/example-hdf-evidence.json
hdf evidence set /tmp/example-hdf-evidence.json --package-id "550e8400-e29b-41d4-a716-446655440000"
hdf evidence set /tmp/example-hdf-evidence.json --description "Quarterly ATO evidence bundle"
hdf validate --type evidence-package /tmp/example-hdf-evidence.json

Expected:

  • Each document validates against its schema at creation time.
  • Plan has 2 assessments derived from system components.
  • Evidence build accepts multiple --results flags, globs, and positional args.
  • evidence verify --checksums-only: all SHA-256 checksums match.
  • evidence verify (default): completeness check reports missing baselines.
  • evidence set --plan-ref: links evidence to the assessment plan.
  • evidence info: displays package name, preparer, timestamp, systemRef, contents list with checksum status, and completeness summary.

12. Compliance Thresholds (CI/CD Gates) ​

Story: A CI pipeline enforces compliance regression detection.

bash
# Generate a threshold from HDF results
hdf generate threshold hdf-schema/test/fixtures/minimal-v2.json -o /tmp/example-hdf-threshold.yaml

# View the generated threshold
cat /tmp/example-hdf-threshold.yaml

# Validate the same results against the threshold (should pass)
hdf validate threshold hdf-schema/test/fixtures/minimal-v2.json -T /tmp/example-hdf-threshold.yaml

# Generate with control IDs included
hdf generate threshold hdf-schema/test/fixtures/minimal-v2.json --include-controls

# Inline threshold validation (CI one-liner)
hdf validate threshold hdf-schema/test/fixtures/minimal-v2.json -I "{compliance.min: 40}"

# Inline threshold that should fail (compliance too high)
hdf validate threshold hdf-schema/test/fixtures/minimal-v2.json -I "{compliance.min: 90}" || echo "Expected failure"

Expected:

  • Generate produces YAML with compliance, passed, failed sections.
  • Validate with -T: exit 0 when results meet threshold.
  • --include-controls: adds control ID lists under each status/severity.
  • Inline -I: parses {key: value} format, exit 0 on pass, exit 1 on fail.
  • Round-trip: generate → validate against same file always passes.

13. Fetching Results from APIs ​

Story: A security engineer pulls findings from an API.

bash
# Check fetch help
hdf fetch --help

Expected: Help text shows available API sources. (Actual API calls require credentials/endpoints.)


14. HTML Reports ​

Story: An assessor hands a reviewer a readable report built from v3 results, without downgrading the document or installing a second tool.

bash
# Full report: status dashboard, components, and every requirement with its test code
hdf convert /tmp/example-hdf-results.json --to html -o /tmp/example-report.html

# Less detail: no test code (manager), or the dashboard and components only (executive)
hdf convert /tmp/example-hdf-results.json --to html --report-type manager -o /tmp/example-report.html
hdf convert /tmp/example-hdf-results.json --to html --report-type executive -o /tmp/example-report.html

Expected: One self-contained HTML file. It shows every component with its type, componentId, labels and external IDs; the runner and systemRef when the document carries them; requirement counts by status and severity with a compliance level; and, for the manager and administrator types, each requirement with its source location, test results, details and any overrides, the effective status beside the assessed one. Enrichment added by hdf enrich (or any other externalReferences[]) appears with the document, baseline, requirement or override it is attached to, with an embedded STIX object shown in full.

Things worth knowing:

  • The file loads nothing from the network. Its content security policy blocks every external request and admits only the report's own small script, by hash, so it opens in an air-gapped environment and can be archived as evidence beside the JSON it came from.
  • Long content is collapsed. Any block longer than two lines or rows starts closed behind its heading, with a count, and ends with a link back to its own top. The results list can be filtered by text and status and expanded or collapsed in one click; printing opens everything.
  • A button in the header switches between the light and dark palettes. Without a choice the report follows the system setting.
  • The same input always produces the same bytes; there is no render-time timestamp. Override expiry is judged at the document's own assessment time (its timestamp, else its latest result start time), not at the time the report is rendered, and the report states that time.
  • Colours meet WCAG 2.2 AA contrast in both the light and the dark palette, status and severity are always stated in text as well as colour, and the page is usable by keyboard and screen reader.
  • --report-type is refused for any target other than html.

Several documents can go into one report. Name the files, a glob, or a directory, and give -o a file:

bash
# Named files, in the order given
hdf convert scan-a.json scan-b.json --to html -o /tmp/example-combined.html

# Every HDF results document under a directory, searched recursively, in path order
hdf convert /tmp/example-scans/ --to html -o /tmp/example-combined.html

# -o <directory>/ keeps one report per input instead
hdf convert scan-a.json scan-b.json --to html -o /tmp/example-reports/

Expected: One report whose dashboard counts every requirement across the inputs, with a "Status by source" table, a panel per source (tool, assessed time, systemRef, runner), every component labelled with the document it came from, and the results grouped by source. Each source's overrides are judged at that document's own assessment time.

  • A directory routinely holds other artifacts beside the results (amendments, OSCAL exports, logs). Files under it that are not HDF results documents are passed over and counted on stderr; legacy (v2) results are upgraded and included. A file you name explicitly is never passed over: if it is not a results document, the command fails and writes nothing.
  • A directory that holds both the legacy and the current form of the same scan contributes both. Point at the files you mean, or use a glob such as scans/*.v3.hdf.json.
  • Inputs that share a file name are shown by path.

Released under the Apache 2.0 License.