Skip to content

audit export publishes request_hash, an unsalted hash over unredacted params, with no statement of its sensitivity #268

Description

@vladimirrott

Scope 1 shipped in #318 on 2026-09-01. Read this as an HMAC issue.
Verified at 30b9e315 on 2026-09-17: docs/cli.md:167 and
docs/the-audit-chain.md:404 both state the confidentiality class, and
tests/release/audit-export-confidentiality.test.sh fails if the two calls
below ever swap order. What is left is Scope 2, which needs an HMAC over the
request and a chain_version bump, so the hard half is the whole issue now.

The two line numbers have also moved twice. At 30b9e315 they are
dispatcher.rs:2224 (compute_request_hash) and dispatcher.rs:2264
(redact_params), not the 2001 and 2041 the paragraph below quotes.

request_hash is a column of every signed chain row, and sysknife audit export (#260) now serialises it. The hash commits to the unredacted request:

crates/sysknife-daemon/src/dispatcher.rs:2001   let request_hash = compute_request_hash(action_name, params);
crates/sysknife-daemon/src/dispatcher.rs:2041   let redacted_params = redact_params(action_name, params);

Redaction runs 40 lines after the hash, and compute_request_hash is one unsalted round:

hasher.update(action_name.as_bytes());
hasher.update(b"\x00");
hasher.update(canonical.as_bytes());

Why it matters

For ConfigureWifi the credential params are password and ssid. The SSID is broadcast, so the only unknown in the preimage is the passphrase, and a single unsalted SHA-256 is the cheapest thing a GPU cracks. The daemon keeps the database 0600 inside a 0700 directory precisely so this content does not travel; an export moves it across that boundary by design, into a SIEM, a ticket, or an auditor's laptop.

High-entropy credentials such as ProAttach tokens are unaffected. The problem is the low-entropy human-chosen ones.

The column cannot simply be dropped: request_hash is part of ChainContent::canonical_bytes (crates/sysknife-daemon/src/audit_chain.rs:508), so an offline verifier needs it to rebuild the signed bytes.

Scope

Two parts, and the first is the one that matters today:

  1. Say plainly in docs/cli.md and docs/the-audit-chain.md that an export inherits the database's confidentiality class and is not a redacted artifact. Done in feat(cli): export audit chain rows as JSON #318 on 2026-09-01, guarded by tests/release/audit-export-confidentiality.test.sh.
  2. Consider replacing the bare hash with an HMAC keyed by the audit key. That keeps the binding an offline verifier needs while removing the offline-crackable commitment, at the cost of a chain_version bump and a verifier that has the key.

Tests first

Part 1's guard is on main already. For part 2, a round-trip test that an HMAC-bound row still verifies, plus a migration test across chain_version, and a negative case: a row written under the old chain_version must still verify after the change, or the bump has silently invalidated every existing trail.

Difficulty

hard. The documentation half that made this medium has shipped. What remains touches the signed encoding, so it needs the HMAC, a chain_version bump and a verifier that holds the key.

Getting started

CONTRIBUTING.md has the build and test commands, and docs/developer-guide.md covers the setup steps and how to reproduce each required check locally. No CLA and no copyright waiver. The project is MIT.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghardDifficulty: crosses a trust boundary or needs hardwarehelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions