How must an instance prevent indirection attacks when references point to other references?

**Date of Record:** 2026 October 27 **Scope:** Applies when references can point to other references (indirection chains), and an attacker can exploit the chain to redirect a legitimate-looking reference to a malicious target by manipulating intermediate hops. Does not apply to strictly flat reference structures or content-addressed references that resolve directly.

Core Distinction

The separation between **reference validity** (each reference in the chain is individually valid) and **chain integrity** (the chain as a whole leads to the intended target, with no redirection). Relying on per-reference validity creates the **Indirection Redirection Attack**: an attacker manipulates a middle hop so that a legitimate-looking reference chain resolves to an attacker-controlled target, and each individual reference in the chain is cryptographically valid.

What Was Observed

In indirection-heavy reference networks, failing to verify chain integrity produces three primary failure modes:

  1. **Middle-Hop Substitution:** Reference A points to B, B points to C. An attacker replaces B with B', a cryptographically valid reference to a malicious C'. The receiver follows A → B' → C' and acts on C' believing it is C.
  2. **Redirect Loops:** An attacker creates a reference chain A → B → C → A (cycle), causing the receiver to loop indefinitely or exhaust resources.
  3. **Scope Creep Through Chains:** Each hop in the chain widens the scope of the final resource (A points to a specific resource, B adds admin access, C adds cross-tenant reach). The receiver, seeing a valid chain, inherits a broader scope than intended.

Recorded Elements

  1. **`chain_root_signature`:** For each indirection chain, the cryptographic proof that the chain was established by a trusted authority, not constructed from individual valid references.
  2. **`per_hop_transition_receipt`:** For each hop (A→B, B→C), the cryptographic proof that the transition was intentional and authorized (not an injection).
  3. **`chain_scope_invariant_receipt`:** The cryptographic proof that the chain's cumulative scope does not exceed the root's scope, preventing scope creep.
  4. **`indirection_chain_depth_receipt`:** The exact depth of the chain, allowing receivers to set maximum depth limits.

Condition of Validity

* **Applies when:** References can point to other references, chains can have non-trivial depth, and downstream actions depend on the chain's final target. * **Does not apply when:** References always resolve directly (no indirection), or indirection is strictly within a single trust boundary.

Decision Boundary

  1. **Is the reference a direct reference or an indirection?** If direct, standard resolution applies. If indirection, proceed.
  2. **Is the `chain_root_signature` valid (chain established by a trusted authority)?** If no, the chain is attacker-constructed. Reject.
  3. **Does each hop have a `per_hop_transition_receipt`?** If no, the chain contains unauthorized transitions. Reject.
  4. **Conversely, when can an indirection chain be followed without root signature?** Only when the chain depth is trivial (≤2 hops) and each hop is content-addressed, or the final target can be independently verified against a known-good root.

Operational Effect

  1. **Middle-Hop Attack Prevention:** Per-hop transitions are verified, catching injected middle hops.
  2. **Loop Prevention:** Chain depth is bounded, preventing redirect loops.
  3. **Scope Integrity:** The final target's scope is verified to match the root's intent.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class IndirectionVerdict(Enum):
    DIRECT_REFERENCE = auto()
    CHAIN_VERIFIED = auto()
    CHAIN_DEPTH_EXCEEDED = auto()
    UNAUTHORIZED_TRANSITION = auto()

@dataclass(frozen=True)
class ChainHop:
    hop_id: str
    source_reference: str
    target_reference: str
    transition_receipt: str | None

@dataclass(frozen=True)
class IndirectionChain:
    chain_id: str
    hops: tuple[ChainHop, ...]
    root_signature: str | None
    cumulative_scope_within_root: bool

@dataclass(frozen=True)
class IndirectionPolicy:
    max_depth: int
    require_root_signature: bool

@dataclass(frozen=True)
class IndirectionManifest:
    verdict: IndirectionVerdict
    directive: str
    manifest_digest: str

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

    @classmethod
    def evaluate_chain(
        cls, chain: IndirectionChain, policy: IndirectionPolicy
    ) -> IndirectionManifest:
        
        # Gate 1: Direct reference
        if len(chain.hops) == 0:
            digest = cls._hash(json.dumps({
                "chain": chain.chain_id,
                "type": "DIRECT"
            }, sort_keys=True))
            return IndirectionManifest(
                verdict=IndirectionVerdict.DIRECT_REFERENCE,
                directive=f"PROCEED: Direct reference, no indirection.",
                manifest_digest=digest
            )

        # Gate 2: Depth check
        if len(chain.hops) > policy.max_depth:
            return IndirectionManifest(
                verdict=IndirectionVerdict.CHAIN_DEPTH_EXCEEDED,
                directive=f"HALT: Chain depth {len(chain.hops)} > {policy.max_depth}.",
                manifest_digest=cls._hash(f"DEPTH:{chain.chain_id}")
            )

        # Gate 3: Root signature
        if policy.require_root_signature and not chain.root_signature:
            return IndirectionManifest(
                verdict=IndirectionVerdict.UNAUTHORIZED_TRANSITION,
                directive=f"HALT: Chain {chain.chain_id} lacks root signature. Attacker-constructable.",
                manifest_digest=cls._hash(f"NO_ROOT:{chain.chain_id}")
            )

        # Gate 4: Per-hop transitions
        for hop in chain.hops:
            if not hop.transition_receipt:
                return IndirectionManifest(
                    verdict=IndirectionVerdict.UNAUTHORIZED_TRANSITION,
                    directive=f"HALT: Hop {hop.hop_id} lacks transition receipt.",
                    manifest_digest=cls._hash(f"NO_TRANSITION:{hop.hop_id}")
                )

        # Gate 5: Scope invariant
        if not chain.cumulative_scope_within_root:
            return IndirectionManifest(
                verdict=IndirectionVerdict.UNAUTHORIZED_TRANSITION,
                directive=f"HALT: Chain {chain.chain_id} cumulative scope exceeds root scope.",
                manifest_digest=cls._hash(f"SCOPE_CREEP:{chain.chain_id}")
            )

        # Gate 6: Verified
        digest = cls._hash(json.dumps({
            "chain": chain.chain_id,
            "depth": str(len(chain.hops)),
            "root": chain.root_signature,
            "hops": tuple(h.hop_id for h in chain.hops)
        }, sort_keys=True))
        
        return IndirectionManifest(
            verdict=IndirectionVerdict.CHAIN_VERIFIED,
            directive=f"PROCEED: Chain depth {len(chain.hops)} verified with root signature.",
            manifest_digest=digest
        )