What model output attribution signatures must survive a shared-tenant inference endpoint?
**Date of Record:** 2026 October 27 **Scope:** Applies when multiple tenants share the same model inference endpoint (multi-tenant API, shared provider account), and a downstream consumer must verify that a specific output was produced by the model for that tenant, not substituted or replayed from another tenant's session. Does not apply to strictly dedicated per-tenant model endpoints or sessions where output attribution is not required.
Core Distinction
The separation between **endpoint authentication** (the caller proved their identity to the inference provider) and **output attribution** (the produced output is cryptographically bound to the specific model, tenant, and prompt). Relying on endpoint authentication creates the **Shared Endpoint Attribution Gap**: the provider authenticated the tenant, but the output could be from a cached response of another tenant, a different model, or a replayed prior response, and the downstream consumer cannot distinguish legitimate output from substitution.
What Was Observed
In multi-tenant inference architectures, failing to preserve output attribution produces three primary failure modes:
- **Cross-Tenant Cache Leak:** The provider's cache returns Tenant B's cached response to Tenant A's query because the prompts happened to match. Tenant A receives data attributed to it but actually generated from Tenant B's session.
- **Model Substitution:** The provider routes the request to a cheaper model version without notification, and the output reflects the cheaper model. Downstream consumers assume the output came from the advertised model.
- **Replay Attack:** An attacker captures a legitimate output (signed by the provider) and replays it as if it were a fresh response to a different prompt. The consumer cannot tell because the signature covers the output but not the prompt binding.
Recorded Elements
- **`prompt_output_binding_signature`:** A signature over the exact prompt, the exact output, the model version, and the tenant ID, issued by the provider, allowing downstream consumers to verify the binding.
- **`model_version_attestation_receipt`:** A cryptographic proof from the provider of which model version produced the output, allowing detection of silent model substitution.
- **`tenant_isolation_attestation`:** A cryptographic proof that the output was not cross-contaminated with another tenant's session (no cache sharing, no routing through another tenant's context).
- **`inference_freshness_signature`:** A timestamped signature proving the output was freshly generated (not replayed from cache) for this prompt, within the expected inference window.
Condition of Validity
* **Applies when:** Multiple tenants share an inference endpoint, downstream consumers depend on output attribution, and substitution or replay is possible. * **Does not apply when:** The endpoint is dedicated per-tenant, or downstream consumers do not care about attribution (e.g., internal exploratory use).
Decision Boundary
- **Is the endpoint shared across tenants?** If no, standard output handling applies. If yes, proceed.
- **Does the output include a `prompt_output_binding_signature`?** If no, the output cannot be attributed. The consumer must treat it as `UNATTRIBUTED`.
- **Does the `model_version_attestation_receipt` match the expected model?** If no, the output came from a different model. Reject.
- **Conversely, when can a consumer accept output without attribution?** Only when the consumer's task is tolerant to cross-tenant contamination and model substitution, or the endpoint is contractually verified as dedicated.
Operational Effect
- **Cross-Tenant Leak Prevention:** Binding prevents cached responses from being served across tenants.
- **Model Integrity:** Attestation prevents silent model substitution.
- **Replay Prevention:** Freshness signatures ensure outputs are newly generated, not replayed.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class AttributionVerdict(Enum):
DEDICATED_ENDPOINT = auto()
ATTRIBUTION_VERIFIED = auto()
ATTRIBUTION_MISSING = auto()
MODEL_MISMATCH = auto()
CROSS_TENANT_SUSPECTED = auto()
@dataclass(frozen=True)
class ModelOutput:
output_id: str
prompt_output_binding_sig: str | None
model_version_attestation: str | None
tenant_isolation_attestation: str | None
freshness_signature: str | None
expected_model_version: str
claimed_model_version: str | None
@dataclass(frozen=True)
class AttributionPolicy:
endpoint_shared: bool
require_tenant_isolation: bool
@dataclass(frozen=True)
class AttributionManifest:
verdict: AttributionVerdict
directive: str
manifest_digest: str
class ModelOutputAttributionGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_output(
cls, output: ModelOutput, policy: AttributionPolicy
) -> AttributionManifest:
# Gate 1: Dedicated endpoint
if not policy.endpoint_shared:
digest = cls._hash(json.dumps({
"output": output.output_id,
"endpoint": "DEDICATED"
}, sort_keys=True))
return AttributionManifest(
verdict=AttributionVerdict.DEDICATED_ENDPOINT,
directive=f"PROCEED: Dedicated endpoint for {output.output_id}.",
manifest_digest=digest
)
# Gate 2: Binding signature
if not output.prompt_output_binding_sig:
return AttributionManifest(
verdict=AttributionVerdict.ATTRIBUTION_MISSING,
directive=f"HALT: Output {output.output_id} lacks binding signature.",
manifest_digest=cls._hash(f"NO_BINDING:{output.output_id}")
)
# Gate 3: Model version match
if not output.model_version_attestation:
return AttributionManifest(
verdict=AttributionVerdict.ATTRIBUTION_MISSING,
directive=f"HALT: Output {output.output_id} lacks model version attestation.",
manifest_digest=cls._hash(f"NO_MODEL_ATTEST:{output.output_id}")
)
if output.claimed_model_version and output.claimed_model_version != output.expected_model_version:
return AttributionManifest(
verdict=AttributionVerdict.MODEL_MISMATCH,
directive=f"HALT: Output {output.output_id} model {output.claimed_model_version} != expected {output.expected_model_version}.",
manifest_digest=cls._hash(f"MODEL_MISMATCH:{output.output_id}")
)
# Gate 4: Tenant isolation
if policy.require_tenant_isolation and not output.tenant_isolation_attestation:
return AttributionManifest(
verdict=AttributionVerdict.CROSS_TENANT_SUSPECTED,
directive=f"HALT: Output {output.output_id} lacks tenant isolation attestation.",
manifest_digest=cls._hash(f"NO_ISOLATION:{output.output_id}")
)
# Gate 5: Freshness
if not output.freshness_signature:
return AttributionManifest(
verdict=AttributionVerdict.ATTRIBUTION_MISSING,
directive=f"HALT: Output {output.output_id} lacks freshness signature. Replay risk.",
manifest_digest=cls._hash(f"NO_FRESHNESS:{output.output_id}")
)
# Gate 6: Verified
digest = cls._hash(json.dumps({
"output": output.output_id,
"binding": output.prompt_output_binding_sig,
"model": output.claimed_model_version or output.expected_model_version,
"isolation": output.tenant_isolation_attestation or "NONE",
"freshness": output.freshness_signature
}, sort_keys=True))
return AttributionManifest(
verdict=AttributionVerdict.ATTRIBUTION_VERIFIED,
directive=f"PROCEED: Output {output.output_id} fully attributed.",
manifest_digest=digest
)