Canonical YAML hashing, hash chain audit verification, divergence detection via webhooks — and what each means for auditability.
The Wardex release gate decides whether a deployment can proceed based on wardex-config.yaml in effect. But how do you prove, weeks or months later, that the configuration that produced that decision was exactly that one, and not something else? Without CPL, the audit log says "CVE-2024-XXXX was blocked with risk appetite 6.0" — but does not bind that decision to an immutable version of the configuration. A team could retroactively alter the policy to justify past decisions.
v2.2 introduces the Configuration Provenance Link — a cryptographic layer that binds every release gate decision to the exact configuration that produced it. No new risk model, no changes to the evaluation engine. The work is in auditability and forensic traceability.
Wardex v2.2 introduces wardex config hash, which computes a deterministic hash over a configuration file. The hash is computed over canonical YAML — the parser strips comments, normalizes whitespace, and reorders keys alphabetically. This guarantees that semantically identical configs produce the same hash regardless of formatting:
$ wardex config hash --config wardex-config.yaml
sha256:1840e526e5bda5a8a79aeede4e48d46288b1236e90b89caee99680436972fdae
$ wardex config hash --config wardex-config.yaml --algorithm blake3
blake3:fe7c61a3e9f4b188d1b58d4e0c0d98b53f3decd04deff13dc517028ddcf34b02
Two algorithms are supported. BLAKE3 offers 10-15x performance over SHA-256 in software. Hashes are always prefixed (sha256: or blake3:) so they are never silently compared across algorithms. Implementation in internal/cpl/hash.go and internal/cpl/blake3.go.
Every wardex-gate-audit.log entry now includes a prev_hash field containing the SHA-256 hash of the previous entry's JSON line. The genesis entry uses "genesis" as reference. The wardex audit verify-chain command validates the full chain sequentially:
$ wardex audit verify-chain --audit-log wardex-gate-audit.log
Audit log hash chain: INTACT
Any tampering — editing a past entry, removing rows, inserting fake entries — breaks the chain and is immediately detectable. Implementation in internal/cpl/chain.go.
The wardex audit verify-link command compares config hashes recorded in the audit log against a directory of archived configurations:
$ wardex audit verify-link \
--audit-log wardex-gate-audit.log \
--config-archive ./config-versions/
Three states per entry:
| State | Meaning | Exit code |
|---|---|---|
| OK | Hash matches an archived config | 0 |
| MISMATCH | Config found, hash differs — tampered after decision | 1 |
| MISSING | No archived config produces this hash | 1 |
Algorithm protection: a sha256: hash is never compared against a blake3: config. Implementation in internal/cpl/verifylink.go.
When MISMATCH or MISSING is detected, the verify-link command can notify an external endpoint via fire-and-forget POST. Configurable in wardex-config.yaml:
notifications:
divergence_webhook:
url: "${WARDEX_SIEM_WEBHOOK_URL}"
auth_env: "WARDEX_SIEM_TOKEN"
timeout_seconds: 5
The webhook is non-blocking — failures do not affect exit codes. The notification payload is the full LinkReport JSON with all entries and summary. Implementation in internal/notification/webhook.go.
CLI flags that override config values — --gate-mode aggregate, --fail-above 8.0, --algorithm blake3 — are now recorded in the cli_overrides field of every gate.evaluated audit entry. This closes an auditability gap where pipeline invocations with non-default flags were indistinguishable from default invocations in the log.
The verify-link and verify-chain commands are designed for periodic CI execution. A weekly workflow validates that no past configuration has been altered:
# .github/workflows/cpl-audit.yml
on:
schedule:
- cron: '0 6 * * 1' # every Monday
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: wardex audit verify-link \
--audit-log wardex-gate-audit.log \
--config-archive ./config-versions/
- run: wardex audit verify-chain \
--audit-log wardex-gate-audit.log
The config hash command integrates into CI pipelines to record the exact configuration version at deploy time:
- name: Record config hash
run: |
echo "CONFIG_HASH=$(wardex config hash --config wardex-config.yaml)" >> $GITHUB_ENV
The divergence webhook sends structured JSON to any HTTP endpoint. The payload includes the full LinkReport with per-entry status. Consuming SIEMs can alert on MISMATCH or MISSING entries as security events warranting investigation.
Canonical YAML over raw file hashing. Hashing the raw file would produce different hashes for configs that differ only in whitespace, comment lines, or key ordering — even though the semantic content is identical. The canonical approach (unmarshal → marshal via yaml.v3) normalizes all formatting differences before hashing, producing a content-addressable hash that is reproducible across environments and tools.
BLAKE3 as optional alternative, not replacement. SHA-256 remains the default for maximum interoperability with legacy systems and tooling. BLAKE3 is offered as an opt-in via --algorithm blake3 for environments that need the performance gain. The prefix convention (sha256: vs blake3:) prevents silent cross-algorithm comparison — a defence-in-depth measure against hash confusion.
Fire-and-forget webhook. The divergence notification is an alert, not a gate. Blocking the pipeline on webhook delivery would create an unnecessary failure domain. The exit code is the source of truth for CI/CD decisions; the webhook is a parallel notification channel that can tolerate transient SIEM unavailability.
No persistent state in audit commands. verify-link and verify-chain are stateless — they read the audit log and config archive fresh on each invocation. There is no database, no cache, no daemon. This preserves the operational model of Wardex (stateless CLI) and eliminates deployment complexity for the audit layer.
cli_overrides as structured field, not audit trail string. Rather than embedding flag information in the human-readable AuditTrail string, CPL records overrides as a typed JSON array (["--gate-mode aggregate", "--algorithm blake3"]). This makes them machine-queryable without parsing prose text, and guarantees that every override is captured — even if the audit trail format changes in a future version.
CPL is additive and backward-compatible. Existing wardex-gate-audit.log files without hash fields are flagged by verify-chain but do not block new entries. The evaluate command continues to write new entries with the full CPL fields. No existing workflows break.
Regulatory frameworks increasingly require demonstrable provenance for automated governance decisions. CRA Article 14, DORA Article 9, and NIS2 Article 21 all mandate audit trails that link decisions to the policies in effect. CPL provides the cryptographic substrate — the pipeline operator provides the archive discipline.