ducaale/xh · error

message-signature: Duplicate covered component identifier

Error message

message-signature: Duplicate covered component identifier: {}

What it means

During HTTP message signature construction (RFC 9421), the covered-components list must contain each component identifier at most once. build_signature_params compares semantic uniqueness keys (parameter order ignored) and bails if the same component id appears twice.

Solutions

  1. Deduplicate the covered-components list before signing
  2. Compare components by their semantic uniqueness key, ignoring parameter order
  3. Reject or merge duplicate component specs at configuration load time

Example fix

// before
let components = vec!["@method", "@target-uri", "@method"];
sign_request(&req, &components, ...)?;
// after
let components: Vec<_> = dedup_by_uniqueness_key(vec!["@method", "@target-uri", "@method"]);
sign_request(&req, &components, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

// validate covered components before sign_request
fn has_duplicates(ids: &[&str]) -> bool {
    let mut seen = std::collections::HashSet::new();
    ids.iter().any(|id| !seen.insert(normalize_component_id(id)))
}
if has_duplicates(&components) { eprintln!("duplicate covered component"); }

Try / catch

match sign_request(&req, &components, &key) {
    Ok(signed) => /* ... */,
    Err(e) if e.to_string().contains("Duplicate covered component") => {
        eprintln!("deduplicate covered components and retry");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling sign_request (directly or via tests) with a covered-components list containing a repeated identifier, even with different parameter ordering that is semantically equivalent.

Common situations: Hand-written @method/@target-uri lists that duplicate an entry; programmatic assembly that appends default components already present; config merging that concatenates component lists.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13). Data as JSON: /api/errors/7541270576e3de10. Report an issue: GitHub.

Appendix: source

Thrown at src/message_signature.rs:157

            HeaderValue::from_str(&value)?,
        );
    }
    Ok(())
}

fn build_signature_params(components: &[String]) -> Result<HttpSignatureParams> {
    let mut component_ids = Vec::new();
    let mut seen = HashSet::new();
    for c in components {
        let normalized = normalize_component_id(c);
        let id = HttpMessageComponentId::try_from(normalized.as_str())
            .with_context(|| format!("message-signature: Invalid component: {}", c))?;
        // RFC 9421 requires each covered component identifier to appear at most once.
        // Equivalence is based on component id semantics, where parameter order does
        // not create a distinct identifier.
        let uniqueness_key = component_uniqueness_key(&id);
        if !seen.insert(uniqueness_key) {
            bail!(
                "message-signature: Duplicate covered component identifier: {}",
                id
            );
        }
        component_ids.push(id);
    }
    HttpSignatureParams::try_new(&component_ids)
        .context("message-signature: Failed to create signature params")
}

/// Build a canonical key for RFC 9421 component-identifier uniqueness checks.
///
/// RFC 9421 treats component identifiers as unique entries in covered components,
/// and two identifiers that differ only by parameter ordering are equivalent.
/// We normalize:
/// - component name (`HttpField` lowercased, derived names preserved), and
/// - parameters (sorted, then joined),
///

View on GitHub (pinned to 2404aceecc)