What invariants prevent compromised recovery attempts from deepening compromise?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session has detected its own compromise (corrupted state, adversarial influence, or degraded integrity) and attempts recovery operations (rollback, replay, or state replacement), and the recovery operations themselves can deepen the compromise—either because the recovery path is compromised or because the recovery amplifies the attacker's leverage. Does not apply to sessions with verified clean recovery paths or sessions that cannot attempt recovery.
Core Distinction
The separation between **recovery attempt** (the session acts to restore integrity) and **recovery safety** (the act does not create new compromise or amplify existing one). Relying on attempts creates the **Recovery Amplification**: the session's recovery operations—whether pre-planned or ad hoc—provide additional surface for the compromise, and the recovery itself becomes the vector for deeper compromise.
What Was Observed
In compromised-agent architectures, failing to bound recovery produces three primary failure modes:
- **Recovery Path Injection:** The session attempts rollback to a pre-compromise state, but the rollback mechanism itself has been compromised, and the rollback writes attacker-controlled state.
- **Replay Attack:** The session replays prior operations to re-establish state, but the replay amplifies the compromise by re-executing adversarial actions from the replay window.
- **Visibility Exposure:** The session's recovery attempts (logs, retries, escalated actions) reveal the compromise to the adversary, allowing the adversary to adapt faster than the recovery.
Recorded Elements
- **`compromise-detection-receipt`:** The exact compromise detected (what, when, scope), so recovery is scoped to the known compromise.
- **`recovery-path-integrity-signature`:** The integrity of the recovery mechanisms (rollback, replay, replacement), so the recovery path itself is verified.
- **`recovery-visibility-receipt`:** The visibility of recovery attempts to the adversary, so visibility exposure is bounded.
- **`compromise-isolation-signature`:** The isolation of the compromise (quarantined components, contained scope), so recovery operates on the isolated compromise rather than the full session.
Condition of Validity
* **Applies when:** The session is compromised, recovery is attempted, and the recovery path or adversary visibility can amplify the compromise. * **Does not apply when:** Recovery paths are verified clean, or the session defers recovery to external handling.
Decision Boundary
- **Has the session detected compromise?** If no, standard operation applies. If yes, proceed.
- **Is the recovery path verified clean (`recovery-path-integrity-signature`)?** If yes, recovery is safe. If no, recovery is itself compromised.
- **Is `recovery-visibility-receipt` bounded?** If yes, visibility exposure is limited. If no, recovery reveals the compromise.
- **Conversely, when can the session attempt recovery without path verification?** Only when the recovery is verified irreversible (cannot itself be captured or manipulated), or the recovery occurs in a fully isolated environment cut off from the adversary.
Operational Effect
- **Path Injection Prevention:** Compromised recovery paths are detected before use.
- **Replay Amplification Prevention:** Replays are bounded by the compromise detection scope.
- **Visibility Bounding:** Recovery attempts are made without revealing compromise.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RecoveryVerdict(Enum):
NO_COMPROMISE = auto()
SAFE_RECOVERY = auto()
COMPROMISED_PATH = auto()
VISIBILITY_RISK = auto()
@dataclass(frozen=True)
class RecoveryAttempt:
attempt_id: str
compromise_detected: bool
recovery_path_verified: bool
recovery_isolated: bool
recovery_visibility_bounded: bool
recovery_irreversible: bool
@dataclass(frozen=True)
class RecoveryPolicy:
require_path_verification: bool
@dataclass(frozen=True)
class RecoveryManifest:
verdict: RecoveryVerdict
directive: str
manifest_digest: str
class CompromisedRecoveryGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_attempt(
cls, attempt: RecoveryAttempt, policy: RecoveryPolicy
) -> RecoveryManifest:
# Gate 1: No compromise
if not attempt.compromise_detected:
digest = cls._hash(json.dumps({
"attempt": attempt.attempt_id,
"compromise": False
}, sort_keys=True))
return RecoveryManifest(
verdict=RecoveryVerdict.NO_COMPROMISE,
directive=f"PROCEED: No compromise for attempt {attempt.attempt_id}.",
manifest_digest=digest
)
# Gate 2: Path verification
if policy.require_path_verification and not attempt.recovery_path_verified:
if not attempt.recovery_isolated and not attempt.recovery_irreversible:
return RecoveryManifest(
verdict=RecoveryVerdict.COMPROMISED_PATH,
directive=f"HALT: Attempt {attempt.attempt_id} recovery path unverified.",
manifest_digest=cls._hash(f"UNVERIFIED:{attempt.attempt_id}")
)
# Gate 3: Visibility risk
if not attempt.recovery_visibility_bounded:
if not attempt.recovery_isolated:
return RecoveryManifest(
verdict=RecoveryVerdict.VISIBILITY_RISK,
directive=f"FLAG: Attempt {attempt.attempt_id} visibility not bounded.",
manifest_digest=cls._hash(f"VISIBILITY:{attempt.attempt_id}")
)
# Gate 4: Safe
digest = cls._hash(json.dumps({
"attempt": attempt.attempt_id,
"path_verified": attempt.recovery_path_verified,
"isolated": attempt.recovery_isolated
}, sort_keys=True))
return RecoveryManifest(
verdict=RecoveryVerdict.SAFE_RECOVERY,
directive=f"PROCEED: Attempt {attempt.attempt_id} recovery safe.",
manifest_digest=digest
)