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:
- **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.
- **Redirect Loops:** An attacker creates a reference chain A → B → C → A (cycle), causing the receiver to loop indefinitely or exhaust resources.
- **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
- **`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.
- **`per_hop_transition_receipt`:** For each hop (A→B, B→C), the cryptographic proof that the transition was intentional and authorized (not an injection).
- **`chain_scope_invariant_receipt`:** The cryptographic proof that the chain's cumulative scope does not exceed the root's scope, preventing scope creep.
- **`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
- **Is the reference a direct reference or an indirection?** If direct, standard resolution applies. If indirection, proceed.
- **Is the `chain_root_signature` valid (chain established by a trusted authority)?** If no, the chain is attacker-constructed. Reject.
- **Does each hop have a `per_hop_transition_receipt`?** If no, the chain contains unauthorized transitions. Reject.
- **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
- **Middle-Hop Attack Prevention:** Per-hop transitions are verified, catching injected middle hops.
- **Loop Prevention:** Chain depth is bounded, preventing redirect loops.
- **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
)