veckl

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: Apache-2.0

README ΒΆ

veckl

Semantic diffs for STIG checklists. veckl parses DISA STIG checklist files β€” CKL (STIG Viewer 2) and CKLB (STIG Viewer 3), such as those produced by Evaluate-STIG β€” and tells you what actually changed between scan cycles:

🚨 1 new CAT I open finding. 2 new open total, 2 remediated.

Instead of eyeballing two checklists side by side in STIG Viewer, or trying to read a raw diff of CKL XML, you get a review-ready report: new opens, remediations, status moves, added/removed checks, and benchmark version bumps β€” as markdown for humans (and PR comments) or JSON for machines.

Why

STIG checklists are compliance artifacts that change every scan cycle, but the file formats fight every tool teams already use for change review. CKL is a wall of XML; CKLB is deeply nested JSON; neither produces a readable git diff. So the "what changed since last cycle?" question gets answered by hand, badly, or not at all.

veckl makes checklists behave like code: keep them in git, and let CI tell reviewers what moved.

Install

go install github.com/hinsonct/veckl/cmd/veckl@latest

Or grab a prebuilt binary from releases. Single static binary β€” nothing to install in your airgap beyond the file itself.

Usage

# Human-readable markdown (default) β€” paste into a PR or pipeline summary
veckl diff last-cycle.ckl this-cycle.ckl

# Machine-readable JSON with a stable schema
veckl diff last-cycle.cklb this-cycle.cklb --format json

# CI gate: print the report, then exit 1 if anything newly opened
veckl diff last-cycle.cklb this-cycle.cklb --fail-on new-open,new-cat1

# Export a checklist as OSCAL assessment-results (JSON, OSCAL 1.1.3)
veckl export this-cycle.cklb > assessment-results.json

# Validate an Evaluate-STIG answer file against the latest checklist
veckl answers answerfiles/RHEL_9_STIG.xml this-cycle.cklb --fail-on orphaned,stale

CKL and CKLB are auto-detected from content, and Combined checklists (multiple STIGs per file) are supported. Comparing files from different hosts produces a prominent warning rather than a silent garbage diff.

What the diff reports
Category Meaning
πŸ”΄ Regression any status β†’ Open
βœ… Remediated Open β†’ NotAFinding / Not_Applicable
πŸ”€ Status change other transitions (e.g. Not_Reviewed β†’ NotAFinding)
βž• / βž– Added / removed checks that appeared or disappeared (usually a benchmark revision)
πŸ“ Text change finding details / comments edited with no status change

When the STIG benchmark version itself changed between cycles, veckl says so up front β€” churn from a STIG revision reads very differently than churn from your systems drifting.

CI gate

--fail-on turns the diff into a pipeline gate. The report always prints first; the gate only decides the exit code, so CI logs show exactly what tripped it.

Condition Fails when
new-open any finding newly opened this cycle (regression or added-as-Open)
new-cat1 a CAT I finding newly opened this cycle
any-open any finding is open in the new checklist
any-cat1 any CAT I finding is open in the new checklist

Exit codes: 0 clean, 1 a --fail-on condition tripped, 2 veckl itself failed (unparseable file, bad flag) β€” so pipelines can distinguish "compliance regressed" from "tool broke".

GitHub Actions

This repo ships a composite action that runs the diff, publishes the report to the job summary, and applies the gate:

- uses: hinsonct/veckl@v0
  with:
    old: checklists/last-cycle.cklb
    new: checklists/this-cycle.cklb
    fail-on: new-open,new-cat1

Outputs report (markdown) and json are available to later steps β€” e.g. to post the report as a PR comment.

GitLab CI

See examples/gitlab-ci.yml for a job that gates the pipeline and keeps the markdown report as an artifact.

OSCAL export

veckl export <checklist> converts a checklist into an OSCAL assessment-results document (JSON, OSCAL 1.1.3): each STIG becomes a result, each check becomes an observation plus a finding whose objective status is satisfied (Not a Finding / Not Applicable) or not-satisfied (Open / Not Reviewed), with the original status and severity preserved as props.

UUIDs are deterministic (UUIDv5 over the document content), so exporting the same checklist twice produces byte-identical output β€” OSCAL artifacts stay diffable in git just like the checklists they came from. Generated documents are validated against the official OSCAL JSON schema in this repo's test suite.

Answer-file validation

Evaluate-STIG answer files automate check responses across scan cycles, and they rot silently. veckl answers <answer-file> <checklist> flags:

  • Orphaned entries β€” the check no longer exists in the benchmark (removed or renumbered by a STIG revision)
  • Stale entries β€” the finding's current status matches none of the statuses the answer can produce, so the entry no longer fires (the system drifted, or the scan ran without the answer file)

Answer keys for other hosts are skipped, DEFAULT keys always apply, and comment-only entries are never flagged. --fail-on orphaned,stale gates CI with the same exit-code convention as diff.

Verifying releases

Release checksums are signed with cosign keyless signing using the release workflow's GitHub OIDC identity β€” no key to distribute, no key to leak. To verify a download (substitute the version):

VER=0.1.0
cosign verify-blob \
  --certificate "veckl_${VER}_checksums.txt.pem" \
  --signature "veckl_${VER}_checksums.txt.sig" \
  --certificate-identity "https://github.com/hinsonct/veckl/.github/workflows/release.yml@refs/tags/v${VER}" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  "veckl_${VER}_checksums.txt"
sha256sum -c "veckl_${VER}_checksums.txt" --ignore-missing

The first command proves the checksum file was produced by this repository's release workflow for that tag; the second proves your archive matches it.

Airgapped delivery (Zarf)

Zarf users can package veckl for transfer across an airgap boundary. On a connected machine:

zarf package create packaging/zarf --set VERSION=0.1.0 --set ARCH=amd64 --confirm

Move the resulting zarf-package-veckl-*.tar.zst across the boundary, then on the inside:

zarf package deploy zarf-package-veckl-*.tar.zst --confirm

This installs the binary to /usr/local/bin/veckl. (Zarf is optional β€” veckl is a single static binary, so copying the file works too; the package just makes the delivery declarative and checksummed.)

How it fits with other tools

  • Evaluate-STIG is the intended producer: it scans your systems and emits CKL/CKLB; veckl diffs consecutive outputs.
  • STIG Manager is a database-backed web application for managing STIG assessments across an organization. veckl is deliberately not that: it's a stateless CLI for git- and CI-centric workflows, and works fine alongside STIG Manager.

Roadmap

  • CI gate: --fail-on new-open,new-cat1 exit codes + a GitHub Action and GitLab CI examples
  • OSCAL export: emit OSCAL assessment-results (JSON, OSCAL 1.1.3), schema-validated in tests
  • Evaluate-STIG answer-file validation: flag stale or orphaned answer-file entries
  • Zarf package for airgapped delivery
  • Signed releases (cosign)

Development

go test ./...
go build ./cmd/veckl

Test fixtures in testdata/ are fully synthetic: public DISA RHEL 9 STIG identifiers with fabricated hosts and results. No real scan data is (or ever will be) in this repository.

License

Apache-2.0

Directories ΒΆ

Path Synopsis
cmd
veckl command
veckl diffs DISA STIG checklists (CKL/CKLB) between scan cycles.
veckl diffs DISA STIG checklists (CKL/CKLB) between scan cycles.
internal
answers
Package answers parses Evaluate-STIG answer files and validates them against a checklist.
Package answers parses Evaluate-STIG answer files and validates them against a checklist.
checklist
Package checklist parses DISA STIG checklist files (CKL and CKLB) into a single normalized model that the rest of veckl operates on.
Package checklist parses DISA STIG checklist files (CKL and CKLB) into a single normalized model that the rest of veckl operates on.
diff
Package diff computes the semantic difference between two checklist scan cycles: what opened, what was remediated, and what changed in between.
Package diff computes the semantic difference between two checklist scan cycles: what opened, what was remediated, and what changed in between.
oscal
Package oscal converts a parsed checklist into an OSCAL assessment-results document (OSCAL 1.1.3, JSON).
Package oscal converts a parsed checklist into an OSCAL assessment-results document (OSCAL 1.1.3, JSON).
render
Package render turns a diff.Report into human- and machine-readable output.
Package render turns a diff.Report into human- and machine-readable output.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL