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:
- **Signature Verification Failures:** Downstream consumers verify a signature and see a valid signature but an unexpected HSM identity, rejecting it as potentially fraudulent.
- **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.
- **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
- **`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.
- **`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.
- **`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.
- **`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
- **Has an HSM failover occurred?** If no, standard HSM operation applies. If yes, proceed.
- **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.
- **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.
- **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
- **Continuity Proof:** Allows downstream consumers to trust the session's signatures across HSM failover.
- **Substitution Detection:** Detects fraudulent or unexpected key reissuance disguised as failover.
- **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
)