# Where must an instance bind its attested identity before calling a cross-org agent peer? **Date of Record:** 2026 November 12 **Scope:** Applies to cross-org agent-to-agent calls where the caller presents a SPIFFE-style SVID or equivalent verifiable workload identity and the callee enforces trust-domain-based authorization. Does not apply to intra-trust-domain calls, mTLS with shared CA within a single org, or cases where identity is asserted by a bearer token minted by a third-party IdP without workload attestation. ## Core Distinction The failure is not "the identity is invalid." It is "the identity is valid but was bound to the wrong workload instance at the wrong moment." A SPIFFE SVID is a statement about a *workload*, not about a *process*, *pod*, or *agent session*. When an agent runtime multiplexes many logical agents over one attested workload (a shared sidecar, a shared proxy, a pooled worker), the SVID proves the workload is trusted — it does not prove *which* agent inside that workload is acting. Cross-org peers that authorize on `spiffe://other-org/agent/billing` are trusting a name that the caller's own sidecar asserted, not a name the peer independently verified end-to-end. The non-obvious boundary: the trust anchor must be bound at the *narrowest* unit the receiving org is willing to authorize against, and that unit must be the one that *originates* the request, not the one that *transports* it. If the transport layer (sidecar/proxy) holds the SVID and the agent merely hands it a payload, the peer's authorization decision is against the transport, not the agent. ## What Was Observed A two-org integration: Org A's "reconciliation agent" called Org B's "ledger agent" via a shared egress proxy. Org A's proxy held a single SVID `spiffe://org-a/ns/agents/sa/shared-egress`. Attestation used the standard k8s workload attestor (namespace + service account + pod UID selector). Org B's authorization policy mapped `spiffe://org-a/ns/agents/sa/shared-egress` to role `reconciler` with read-only ledger scope. A second Org A agent — the "settlement agent," which should have had write scope but was never granted it — was scheduled onto the same node and, due to a rollout, its pod inherited the same service account. Its calls to Org B succeeded with `reconciler` scope. No policy was violated *as written*: Org B authorized the SVID it received, and the SVID was genuinely issued to that workload. The invariant that failed was the implicit one: "the SVID identifies the agent." It identified the egress workload. The settlement agent's calls were authorized as if they came from the reconciliation agent. A second observation, subtler: during SVID rotation (1h TTL), the proxy kept the old SVID for up to 90s past expiry because rotation was lazy (on next request). Org B's peer had a 60s clock-skew tolerance and a hard `notAfter` check. Between t=60s and t=90s past expiry, calls failed with `SVID expired` even though the proxy believed the SVID was current. The failure was intermittent and correlated with low request rate, not with load. ## Condition of Validity The "SVID identifies the agent" invariant holds only when all of the following are true: 1. **One attested workload per logical agent identity.** The SPIFFE ID's path component (e.g., `.../sa/reconciler`) maps 1:1 to a service account (or equivalent attestation selector) used by exactly one logical agent. 2. **Attestation selectors are agent-specific.** The workload attestor's selectors include something that distinguishes the agent — not just namespace and node, but service account, pod label set, or a per-agent UID. Namespace-only or node-only selectors collapse multiple agents onto one identity. 3. **The SVID is presented by the process that originates the request.** If a sidecar presents the SVID on behalf of the agent, the sidecar must be able to prove *which* local agent it is proxying, and the peer must be able to verify that binding (e.g., the sidecar forwards the agent's own attestation evidence, or the SVID is per-agent and the sidecar holds N of them). 4. **Rotation is eager, not lazy.** The SVID in use is refreshed before `notAfter` minus the peer's tolerance, regardless of request rate. Lazy rotation fails on idle-then-burst traffic. Violating (1) or (2) produces *authorization widening* (the observed settlement-agent case). Violating (3) produces *authorization laundering* — a low-privilege agent can borrow a high-privilege sidecar's identity by routing through it. Violating (4) produces *sporadic expiry* that looks like network flakiness. ## Decision Boundary Before allowing a cross-org call, the caller's runtime must answer: *does the SVID I am about to present attest to the agent making this call, or to a workload that happens to host it?* - If the SVID's selectors include the agent's own service account / pod label / UID → **bind and call.** The peer's authorization is meaningful. - If the SVID is held by a shared transport (proxy, sidecar, gateway) and the agent is identified only by an in-process header or context → **do not call cross-org.** Either mint a per-agent SVID (SPIFFE supports multiple SVIDs per workload via the Workload API) or have the sidecar forward the agent's attestation evidence so the peer can re-verify. - If the SVID is within `rotation_grace` of expiry and the request rate is below `1/rotation_interval` → **force refresh before calling.** Do not rely on lazy rotation. - If the peer's trust bundle does not include the caller's trust domain *and* the caller cannot present a federated bundle → **do not call.** SPIFFE federation is not transitive by default; a bundle that trusts `org-a` does not trust `org-a`'s federated partners. The boundary is not "is the SVID valid" — it is "does the SVID's selector set intersect the agent's identity at the granularity the peer authorizes at." If the intersection is empty or only at the transport layer, the call must be refused locally rather than sent and rejected (or worse, accepted) remotely. ## Operational Effect Enforcing the boundary at the caller changes three things. First, it converts a class of silent authorization widening into a local, pre-flight error — the settlement agent fails at its own runtime with a clear "no agent-scoped SVID" rather than succeeding with the wrong role at the peer. Second, it forces the identity topology to match the authorization topology: if Org B authorizes per-agent, Org A must issue per-agent SVIDs; a shared egress SVID is only acceptable if Org B authorizes at the egress granularity, which it usually should not for cross-org. Third, eager rotation removes the low-rate expiry tail, at the cost of a background refresh loop per SVID — acceptable, since SVID issuance is cheap and the Workload API is designed for it. The residual risk: a compromised sidecar can still present any SVID it holds. The boundary does not prevent that; it only ensures the sidecar holds the *right* SVID for the agent it is proxying, so a compromise is bounded to the agents that sidecar serves rather than to every agent in the namespace. ```python import time from dataclasses import dataclass from typing import Optional class AgentIdentityBindingError(RuntimeError): """Raised when an SVID does not attest to the agent originating a cross-org call.""" @dataclass(frozen=True) class Svid: spiffe_id: str selectors: frozenset[str] # e.g. {"sa:reconciler", "ns:agents", "pod-uid:abc"} not_after: float # unix seconds trust_domains: frozenset[str] @dataclass(frozen=True) class AgentIdentity: agent_name: str required_selector: str # the selector that MUST appear in the SVID trust_domain: str def bind_for_cross_org_call( agent: AgentIdentity, svid: Optional[Svid], peer_trust_domains: frozenset[str], rotation_grace_s: float = 90.0, peer_clock_skew_s: float = 60.0, now: Optional[float] = None, ) -> Svid: now = time.time() if now is None else now if svid is None: raise AgentIdentityBindingError( f"agent {agent.agent_name!r} has no SVID; refusing cross-org call" ) # 1. The SVID must attest to *this agent*, not merely to a host workload. if agent.required_selector not in svid.selectors: raise AgentIdentityBindingError( f"SVID {svid.spiffe_id!r} selectors {sorted(svid.selectors)} do not " f"include agent selector {agent.required_selector!r}; " f"identity is bound to transport, not agent {agent.agent_name!r}" ) # 2. Trust domain must be mutually federated. if agent.trust_domain not in peer_trust_domains: raise AgentIdentityBindingError( f"trust domain {agent.trust_domain!r} not in peer bundle " f"{sorted(peer_trust_domains)}; federation is not transitive" ) if not svid.spiffe_id.startswith(f"spiffe://{agent.trust_domain}/"): raise AgentIdentityBindingError( f"SVID {svid.spiffe_id!r} is outside agent trust domain " f"{agent.trust_domain!r}" ) # 3. Eager rotation: refuse if within grace window of expiry, regardless of rate. remaining = svid.not_after - now if remaining <= rotation_grace_s + peer_clock_skew_s: raise AgentIdentityBindingError( f"SVID {svid.spiffe_id!r} expires in {remaining:.1f}s " f"(grace {rotation_grace_s}s + skew {peer_clock_skew_s}s); " f"force refresh before cross-org call" ) return svid ```