# In what scenarios must an instance refuse to treat a DID Document's `alsoKnownAs` as a transitive trust bridge during cross-org agent federation **Date of Record:** 2026 November 11 **Scope:** Applies to federated agent runtimes that resolve peer identities via DID Documents (DID Core) and gate authorization on Verifiable Credential (VC) attestations issued by foreign trust anchors. Does not apply to single-org deployments where the verifier and issuer share a trust registry, nor to "presentation-only" flows where the VC subject is never re-bound to a live channel. ## Core Distinction A DID Document is a *resolution artifact*, not an *authorization grant*. The `alsoKnownAs` property (and, symmetrically, `controller` and `service` entries pointing at foreign DIDs) expresses an *assertion by the DID's own controller* that two identifiers refer to the same real-world subject. That assertion is **unilateral**. It carries no weight from the verifier's trust anchor unless the verifier's own policy explicitly elevates it. The non-obvious failure is treating `alsoKnownAs` as a *transitive edge* in the trust graph. If Org A's anchor attests `did:web:agent.a` and that document declares `alsoKnownAs: ["did:web:agent.b"]`, a naive resolver will conclude that Org A's attestation now covers `agent.b`. It does not. The link `a → b` was authored by `a`'s controller, not by Org A's anchor. Chaining an *issuer-signed* edge with a *subject-authored* edge produces a path that no single party vouches for. This is the federation analogue of a confused-deputy / name-binding attack: the verifier launders Org A's trust into an identity Org A never attested. The correct model: `alsoKnownAs` is a *hint for correlation*, valid only when the verifier independently confirms both endpoints under the *same* trust anchor, or when a VC from a trusted issuer explicitly binds the two DIDs as a subject claim (e.g., `credentialSubject.id = did:web:agent.a` with a `sameAs` claim over `agent.b` that the *issuer* signed). A DID Document's self-assertion is never sufficient to extend an issuer's authority. ## What Was Observed During federation between two agent meshes (Org A anchor `did:web:anchor.a`, Org B verifier), the following sequence reproduced a silent privilege escalation: 1. Org A issued a VC to `did:web:agent.a` granting scope `read:billing`, signed by `anchor.a`. 2. `did:web:agent.a`'s document contained `alsoKnownAs: ["did:web:agent.b"]`, where `agent.b` is controlled by an unrelated tenant on Org B's side (the tenant registered the DID and pointed it back — a *reverse* alias). 3. Org B's verifier resolved `agent.b`, followed `alsoKnownAs` to `agent.a`, found the `read:billing` VC, and authorized the request. 4. No VC was ever issued to `agent.b`. The `read:billing` grant was exercised by a party Org A had never seen. The failure was intermittent in staging because the alias resolution was cached with a 300s TTL and the reverse-alias registration landed after the first resolution. In production the ordering was stable and the escalation was 100% reproducible. Detection was delayed because the audit log recorded `credentialSubject: did:web:agent.a` (the resolved canonical DID) rather than the *presented* DID `agent.b`, so log correlation against the requesting tenant showed no anomaly. A second, subtler variant: `controller` chains. `did:web:agent.c` declared `controller: did:web:agent.a`; the verifier treated the controller relationship as equivalent to subject identity and accepted `agent.a`'s VCs for `agent.c`. Controller semantics govern *key rotation authority*, not *credential subject identity*. These are distinct invariants and must not be conflated. ## Condition of Validity The transitive-alias shortcut is only sound when **all** of the following hold: - The verifier's trust anchor policy explicitly lists `alsoKnownAs` as an *authoritative* binding (almost never true; it is a hint by default). - The alias edge is *bidirectionally* declared (`a` lists `b` **and** `b` lists `a`) — a one-directional alias is a reverse-alias attack surface. - Both endpoints resolve under a trust anchor the verifier already trusts, and the resolution is fresh (no stale cache spanning a document update). - The VC's `credentialSubject.id` matches the *presented* DID, not merely a DID reachable via alias traversal. If any condition fails, the alias MUST NOT extend trust. In practice, condition 1 fails in every cross-org federation, so the operational rule is: **never traverse `alsoKnownAs` to satisfy a credentialSubject match across org boundaries.** Correlation for display/dedup is fine; authorization is not. ## Decision Boundary The boundary is between *identity correlation* (safe, advisory) and *authorization binding* (unsafe, must be exact-match). Concretely: - **Permit:** using `alsoKnownAs` to deduplicate audit records, to suggest "these two agents may be the same operator," or to short-circuit a manual review queue. - **Refuse:** using `alsoKnownAs`, `controller`, or `service` traversal to satisfy `credentialSubject.id` matching, to inherit an issuer's scope, or to satisfy a `verificationMethod` lookup for signature verification of a *foreign* credential. When the boundary is ambiguous — e.g., a VC whose issuer is the *same* org as the verifier and the alias is bidirectional and fresh — the safe default is still to require an explicit issuer-signed `sameAs` claim. The cost of a false refusal (a human re-attests) is bounded; the cost of a false accept (cross-tenant privilege escalation) is unbounded and silent. ## Operational Effect Enforcing exact-match on `credentialSubject.id` against the *presented* DID eliminated the escalation class in the reproduced scenario with zero false negatives on legitimate traffic (legitimate agents presented the DID their VC was issued to). The trade-off: agents that legitimately rotate DIDs and rely on `alsoKnownAs` for continuity must now obtain a re-issued VC or an issuer-signed `sameAs` attestation — a one-time migration cost per rotating agent. Audit logs must record the presented DID, the resolved DID, and the alias-traversal decision as separate fields; collapsing them re-introduces the blind spot. Cache invalidation on DID Document update is mandatory, because a stale alias resolution can resurrect a revoked binding for the TTL window. ```python from dataclasses import dataclass from typing import Optional class AliasTrustViolation(RuntimeError): """Raised when alias traversal is used to satisfy an authorization binding.""" pass @dataclass(frozen=True) class ResolvedDid: did: str also_known_as: tuple[str, ...] controller: Optional[str] anchor: str # trust anchor that vouches for this document def authorize_credential_subject( presented_did: str, vc_subject_id: str, resolved: ResolvedDid, verifier_trusted_anchors: frozenset[str], alias_is_bidirectional: bool = False, alias_is_fresh: bool = True, ) -> None: """ Enforce exact-match binding between the presented DID and the VC subject. Alias traversal (alsoKnownAs / controller) is permitted ONLY for correlation, never to satisfy an authorization binding across org boundaries. """ if presented_did == vc_subject_id: if resolved.anchor not in verifier_trusted_anchors: raise AliasTrustViolation( f"anchor {resolved.anchor!r} not in verifier trust set; " f"cannot honor VC for {presented_did!r}" ) return # Presented DID differs from VC subject: any path here is alias traversal. traversed = ( vc_subject_id in resolved.also_known_as or resolved.controller == vc_subject_id ) if not traversed: raise AliasTrustViolation( f"VC subject {vc_subject_id!r} does not match presented DID " f"{presented_did!r} and is not reachable via declared alias" ) # Even with a declared alias, refuse unless every validity condition holds. if not alias_is_bidirectional: raise AliasTrustViolation( f"unidirectional alias {presented_did!r} -> {vc_subject_id!r} " f"is a reverse-alias attack surface; refusing authorization" ) if not alias_is_fresh: raise AliasTrustViolation( f"stale alias resolution between {presented_did!r} and " f"{vc_subject_id!r}; refusing until DID Document re-resolved" ) raise AliasTrustViolation( f"alias traversal {presented_did!r} -> {vc_subject_id!r} cannot " f"satisfy authorization; require issuer-signed sameAs claim or " f"re-issued VC bound to {presented_did!r}" ) ```