zeroclaw-labs/zeroclaw · info

HMAC accepts any key length

Error message

HMAC accepts any key length

What it means

During receipt verification, HmacSha256::new_from_slice() returns a Result that is unwrapped with expect("HMAC accepts any key length"). HMAC-SHA256 legally accepts keys of any length, and the generator's key is always a random 32-byte value, so this expect encodes a static invariant and is unreachable in practice.

Source

Thrown at crates/zeroclaw-runtime/src/agent/tool_receipts.rs:83

    }

    /// Verify a receipt against the expected tool execution parameters.
    /// Parses the timestamp from the receipt string, recomputes the HMAC,
    /// and compares. Returns `false` for malformed, tampered, or fabricated receipts.
    pub fn verify(
        &self,
        receipt: &str,
        tool_name: &str,
        args: &serde_json::Value,
        result: &str,
    ) -> bool {
        let Some((timestamp, provided_hash)) = parse_receipt(receipt) else {
            return false;
        };
        let Ok(provided_bytes) = URL_SAFE_NO_PAD.decode(provided_hash) else {
            return false;
        };
        let mut mac = HmacSha256::new_from_slice(&self.key).expect("HMAC accepts any key length");
        mac.update(tool_name.as_bytes());
        mac.update(b"|");
        mac.update(args.to_string().as_bytes());
        mac.update(b"|");
        mac.update(result.as_bytes());
        mac.update(b"|");
        mac.update(timestamp.to_string().as_bytes());
        mac.verify_slice(&provided_bytes).is_ok()
    }

    fn compute_hmac(
        &self,
        tool_name: &str,
        args: &serde_json::Value,
        result: &str,
        timestamp: u64,
    ) -> Vec<u8> {
        let mut mac = HmacSha256::new_from_slice(&self.key).expect("HMAC accepts any key length");

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Treat a hit here as a code-regression signal: inspect ReceiptKeyGenerator for a changed key length or an altered construction path.
  2. Upgrade to the latest zeroclaw version in case a refactor already addressed it.
  3. If forking, keep the key at 32 bytes or switch to new_from_slice error propagation.
Defensive patterns

Strategy: validation

Validate before calling

// If you construct generators from custom key material, reject empty keys up front:
fn make_generator(key: Vec<u8>) -> anyhow::Result<ReceiptKeyGenerator> {
    anyhow::ensure!(!key.is_empty(), "receipt key must be non-empty");
    Ok(ReceiptKeyGenerator::with_key(key))
}

Prevention

When it happens

Trigger: No runtime input reaches this panic: the key comes from ReceiptKeyGenerator, which always produces a 32-byte key (or a test key via with_key). It could only fire if the code were refactored to feed new_from_slice a value that violates HMAC's key contract, which cannot happen by length alone.

Common situations: None for users; maintainers see it in stack traces only if a future refactor changes the key source. Its presence in traces usually just marks the verification path (verify -> jws_verify and the call_tool_* recovery flows), not a real fault.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/281a4adac0be728f. Report an issue: GitHub.