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
- Treat a hit here as a code-regression signal: inspect ReceiptKeyGenerator for a changed key length or an altered construction path.
- Upgrade to the latest zeroclaw version in case a refactor already addressed it.
- 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
- Do not modify ReceiptKeyGenerator's 32-byte key contract without updating the HMAC invariants.
- Rely on verify()'s bool return for all untrusted input; it never panics on malformed receipts.
- Run the call_tool_* receipt tests after touching tool_receipts.rs.
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
- HMAC accepts any key length
- Schema missing required 'type' field
- Request timestamp too old or too far in future
- Encrypted value too short (missing nonce)
- Key file must contain exactly 32 bytes (got {})
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/281a4adac0be728f.
Report an issue: GitHub.