What identity continuity invariants must survive a hardware security module failover?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session uses an HSM (or cloud KMS) for cryptographic operations, the HSM undergoes a failover (primary to replica, region to region), and the session's identity must remain continuous to downstream consumers despite the HSM change. Does not apply to sessions using software-based key management with no HSM failover, or sessions where HSM failover has no external identity consequences.

Core Distinction

The separation between **key identity** (the same logical key is accessible) and **HSM continuity proof** (the key behind the HSM failover is provably the same key, not a reissued or different key under the same identifier). Relying on key identity creates the **HSM Substitution Hazard**: the HSM fails over, the new HSM has a different physical root, but the logical key ID is the same. Downstream consumers cannot distinguish a legitimate failover from a key substitution unless the session provides cryptographic proof of continuity.

What Was Observed

In HSM-backed agent architectures, failing to preserve identity continuity produces three primary failure modes:

  1. **Signature Verification Failures:** Downstream consumers verify a signature and see a valid signature but an unexpected HSM identity, rejecting it as potentially fraudulent.
  2. **Key Reissuance Confusion:** The HSM failover occurs due to key compromise, and the new HSM issues a new key under the same logical ID. Downstream consumers cannot distinguish this from a legitimate failover.
  3. **Audit Trail Break:** The signature chain shows signatures from different HSM identities, and auditors cannot determine whether the signatures are from the same key holder or a different one.

Recorded Elements

  1. **`hsm_root_attestation_chain`:** The cryptographic chain of attestations from the HSM's hardware root through the key ID, proving the key is held by an authorized HSM instance.
  2. **`failover_continuity_proof`:** A cryptographic proof from the HSM cluster that the key was logically continuous across the failover (same key material, same policy constraints), not reissued.
  3. **`key_material_fingerprint`:** The fingerprint of the key material (public key or signature over a known message), allowing downstream consumers to verify the same key is being used.
  4. **`failover_epoch_signature`:** The HSM cluster's signature confirming the failover event at a specific epoch, correlating the failover with subsequent signatures.

Condition of Validity

* **Applies when:** The HSM can failover, downstream consumers verify signatures from the session, and key identity matters for trust. * **Does not apply when:** The HSM is guaranteed not to failover (single-instance with no replica), or downstream consumers verify signatures purely by key material regardless of HSM identity.

Decision Boundary

  1. **Has an HSM failover occurred?** If no, standard HSM operation applies. If yes, proceed.
  2. **Is the `failover_continuity_proof` present?** If no, downstream consumers cannot verify the key is the same. The session must obtain this proof from the HSM cluster.
  3. **Does the `key_material_fingerprint` match the pre-failover fingerprint?** If yes, the key is mathematically the same. If no, the key was reissued, requiring re-registration.
  4. **Conversely, when can a session sign without failover proof?** Only when the failover is documented out-of-band (e.g., a published provider incident) and the key material fingerprint matches, or the signing is for non-trust-critical operations.

Operational Effect

  1. **Continuity Proof:** Allows downstream consumers to trust the session's signatures across HSM failover.
  2. **Substitution Detection:** Detects fraudulent or unexpected key reissuance disguised as failover.
  3. **Audit Clarity:** Provides a cryptographic record of the failover and key continuity.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class HSFVerdict(Enum):
    NO_FAILOVER = auto()
    CONTINUITY_VERIFIED = auto()
    KEY_REISSUED = auto()
    UNVERIFIED_FAILOVER = auto()

@dataclass(frozen=True)
class HSMState:
    failover_occurred: bool
    continuity_proof: str | None
    pre_failover_key_fingerprint: str
    post_failover_key_fingerprint: str
    failover_epoch: float | None

@dataclass(frozen=True)
class HSMPolicy:
    require_continuity_proof: bool

@dataclass(frozen=True)
class HSMManifest:
    verdict: HSFVerdict
    directive: str
    manifest_digest: str

class HSMFailoverGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_failover(
        cls, state: HSMState, policy: HSMPolicy
    ) -> HSMManifest:
        
        # Gate 1: No failover
        if not state.failover_occurred:
            digest = cls._hash(json.dumps({
                "key": state.pre_failover_key_fingerprint,
                "status": "NO_FAILOVER"
            }, sort_keys=True))
            return HSMManifest(
                verdict=HSFVerdict.NO_FAILOVER,
                directive="PROCEED: No HSM failover occurred.",
                manifest_digest=digest
            )

        # Gate 2: Key material match
        if state.pre_failover_key_fingerprint != state.post_failover_key_fingerprint:
            return HSMManifest(
                verdict=HSFVerdict.KEY_REISSUED,
                directive=f"HALT: Key material changed: {state.pre_failover_key_fingerprint} -> {state.post_failover_key_fingerprint}. Re-registration required.",
                manifest_digest=cls._hash(f"REISSUED:{state.post_failover_key_fingerprint}")
            )

        # Gate 3: Continuity proof
        if policy.require_continuity_proof and not state.continuity_proof:
            return HSMManifest(
                verdict=HSFVerdict.UNVERIFIED_FAILOVER,
                directive="HALT: Failover occurred but no continuity proof. Downstream verification may fail.",
                manifest_digest=cls._hash(f"NO_PROOF:{state.failover_epoch:.0f}")
            )

        # Gate 4: Verified
        digest = cls._hash(json.dumps({
            "key": state.post_failover_key_fingerprint,
            "proof": state.continuity_proof or "NONE",
            "epoch": f"{state.failover_epoch:.0f}" if state.failover_epoch else "N/A"
        }, sort_keys=True))
        
        return HSMManifest(
            verdict=HSFVerdict.CONTINUITY_VERIFIED,
            directive=f"PROCEED: HSM failover verified, key continuous.",
            manifest_digest=digest
        )