AID-H-027: Inference Cache Integrity, Partitioning & Safe Reuse
Pillars: app, infra
Lifecycle: building, operation
Scope boundary
Owns integrity, authorization-context binding, tenant partitioning, authenticated envelopes, invalidation generations, and safe reuse decisions for inference-cache entries. It does not design the broader shared-serving topology or emit operational anomaly findings.
AID-H-032.002: AID-H-027 decides whether a cached object is authentic and safe to reuse for the exact security context; AID-H-032.002 isolates queues, batching, KV or prefix state, workers, and other serving state across tenants. A correct cache key does not establish tenant-isolated serving topology.
Warning
Medium on Latency & Cache Hit Rate
Strict cache isolation and invalidation reduce cache hit rate and may increase inference latency or infrastructure cost. This trade-off is usually justified for multi-tenant systems, privileged agent workflows, or systems with sensitive retrieval content because unsafe cache reuse can bypass normal validation paths.
Implementation guidance
- AID-H-027-G001: Bind every cache key to tenant, session, user or agent identity, model version, system-prompt or policy version, and authorization context so cached entries cannot be reused across incompatible trust boundaries.
- AID-H-027-G002: Partition cache objects and cache reuse keys by tenant and security context, and hand serving-topology isolation decisions to the serving-state control.
- AID-H-027-G003: Before release, test whether cache policy creates a prohibited hit/miss timing signal and require padding, jitter, bypass, or a separate namespace when it does.
- AID-H-027-G004: Fail closed on cache reuse when provenance or isolation metadata is missing or unverifiable.
- AID-H-027-G005: Invalidate cache entries on model updates, prompt-template changes, tool-policy changes, or corpus/version changes that would make prior cached outputs unsafe or stale.
- AID-H-027-G006: Log cache hits, cache misses, cache-source provenance, and invalidation events so analysts can distinguish fresh generation from reused output during forensics and assurance review.
AID-H-027-G001
Apply this control at the authenticated inference gateway before every exact-match or semantic-cache lookup and write. The gateway, not model output or caller-supplied JSON, derives the tenant, subject, session, agent, authorization claims, model release, system-prompt digest, policy digest, and retrieval-corpus release. A semantic cache must use this context as a mandatory namespace filter before similarity search.
Action: construct a canonical, secret-keyed cache identity
# File: cache/context_key.py
from __future__ import annotations
import hashlib
import hmac
import json
import re
from dataclasses import asdict, dataclass
SHA256 = re.compile(r"^[a-f0-9]{64}$")
KEY_ID = re.compile(r"^[A-Za-z0-9._-]{1,64}$")
@dataclass(frozen=True)
class CacheContext:
tenant_id: str
subject_id: str
session_id: str
agent_id: str
model_release: str
system_prompt_sha256: str
policy_sha256: str
corpus_release: str
authorization_claims: tuple[str, ...]
def require_text(name: str, value: str) -> str:
if not isinstance(value, str) or not value or len(value.encode("utf-8")) > 512:
raise ValueError(f"{name}_invalid")
if any(ord(character) < 0x20 for character in value):
raise ValueError(f"{name}_contains_control_character")
return value
def build_cache_key(
context: CacheContext, *, key_id: str, key: bytes
) -> tuple[str, dict]:
if not KEY_ID.fullmatch(key_id):
raise ValueError("key_id_invalid")
if not isinstance(key, bytes) or len(key) < 32:
raise ValueError("cache_key_secret_too_short")
for name in (
"tenant_id", "subject_id", "session_id", "agent_id",
"model_release", "corpus_release",
):
require_text(name, getattr(context, name))
for name in ("system_prompt_sha256", "policy_sha256"):
if not SHA256.fullmatch(getattr(context, name)):
raise ValueError(f"{name}_invalid")
claims = context.authorization_claims
if (
not isinstance(claims, tuple)
or not claims
or list(claims) != sorted(set(claims))
):
raise ValueError("authorization_claims_must_be_sorted_unique")
for claim in claims:
require_text("authorization_claim", claim)
material = (
json.dumps(
{"schema_version": "aidefend.h027-cache-context.v1", **asdict(context)},
sort_keys=True, separators=(",", ":"), allow_nan=False,
) + "\n"
).encode("utf-8")
context_sha256 = hashlib.sha256(material).hexdigest()
key_digest = hmac.new(key, material, hashlib.sha256).hexdigest()
cache_key = f"scache:{key_id}:{key_digest}"
return cache_key, {
"schema_version": "aidefend.h027-cache-key-receipt.v1",
"context_sha256": context_sha256,
"cache_key_sha256": hashlib.sha256(cache_key.encode("ascii")).hexdigest(),
"key_id": key_id,
}Evidence, independent verification, and outcomes
Load the HMAC key from the workload secret manager; never place it in Redis, logs, or request parameters. For every cache lookup, retain a signed receipt binding the request ID, authenticated identity receipt, context digest, key ID, cache-key digest, model/prompt/policy/corpus releases, and hit or miss decision. Reconcile receipts against the complete inference-gateway request population. An independent verifier re-derives contexts from authenticated request records, resolves the historical key by key ID, recomputes the key, and tests that changing any one dimension cannot hit the original entry.
AID-H-027-G002
Use this control when a cache fabric or serving engine can reuse response, semantic, prefix, or KV state. It owns opaque cache namespaces and reuse salts. Queue, batching, warm-worker, failover, and dedicated-pool placement remain with AID-H-032.002; a dependency receipt from that control does not create a local PASS.
Action: derive opaque partitions and enforce them at each cache API
# File: cache/tenant_partition.py
from __future__ import annotations
import hashlib
import hmac
import re
import redis
SHA256 = re.compile(r"^[a-f0-9]{64}$")
def partition_id(
tenant_id: str, security_context: str, *, partition_key: bytes
) -> str:
if not tenant_id or not security_context:
raise ValueError("tenant_and_security_context_required")
if len(partition_key) < 32:
raise ValueError("partition_key_too_short")
material = tenant_id.encode("utf-8") + b"\x00" + security_context.encode("utf-8")
return hmac.new(partition_key, material, hashlib.sha256).hexdigest()
class PartitionedCache:
def __init__(
self, client: redis.Redis, partition: str, generation: int
) -> None:
if not SHA256.fullmatch(partition):
raise ValueError("partition_id_invalid")
if isinstance(generation, bool) or not isinstance(generation, int) or generation < 1:
raise ValueError("generation_invalid")
self.client = client
self.prefix = f"scache:{partition}:g{generation}:"
def _key(self, context_key_sha256: str) -> str:
if not SHA256.fullmatch(context_key_sha256):
raise ValueError("context_key_sha256_invalid")
return self.prefix + context_key_sha256
def get(self, context_key_sha256: str) -> bytes | None:
return self.client.get(self._key(context_key_sha256))
def set(self, context_key_sha256: str, value: bytes, ttl_seconds: int) -> None:
if isinstance(ttl_seconds, bool) or not isinstance(ttl_seconds, int) or ttl_seconds < 1:
raise ValueError("ttl_seconds_invalid")
self.client.set(self._key(context_key_sha256), value, ex=ttl_seconds)Action: bind serving-engine reuse to the same partition
# File: serving/vllm_partition.py
import hashlib
import hmac
def vllm_cache_salt(
partition: str, model_release: str, *, salt_key: bytes
) -> str:
if len(salt_key) < 32 or not partition or not model_release:
raise ValueError("cache_salt_input_invalid")
return hmac.new(
salt_key,
f"{partition}\x00{model_release}".encode("utf-8"),
hashlib.sha256,
).hexdigest()
def openai_extra_body(
partition: str, model_release: str, *, salt_key: bytes
) -> dict:
return {
"cache_salt": vllm_cache_salt(
partition, model_release, salt_key=salt_key
)
}Pass the returned object as extra_body only to a tested serving release that documents cache_salt. If the engine lacks an enforceable reuse key, emit a signed AID-H-032.002 placement finding and disable shared prefix/KV reuse or route the workload to an isolated tier. Configure Redis TLS and ACL identities so application roles can reach only approved trust-zone databases; opaque prefixes are not authorization by themselves.
Evidence, replay, and outcomes
Retain the signed partition-policy release, secret key IDs, Redis endpoint/TLS/ACL identity, cache and serving-engine versions, generation, request-to-partition receipts, reuse-salt digests, configuration readback, and complete gateway request population. Independently replay cross-tenant and cross-security-context reads, direct Redis access attempts, key/salt rotation, missing engine hooks, restart, and failover.
AID-H-027-G003
Run this before production and after serving, scheduler, tokenizer, cache, model, hardware-class, or routing changes whenever prefix or KV reuse can cross tenant identities. Use only synthetic prompts. A versioned policy supplies trial count, bootstrap count, confidence quantile, material-delta threshold, routing constraints, and analysis seed; this framework publishes no universal threshold.
Action: run randomized paired cold-versus-victim-warmed trials
# File: serving/cache_timing_gate.py
from __future__ import annotations
import math
import random
import secrets
import statistics
from dataclasses import asdict, dataclass
from typing import Callable
MeasureTTFT = Callable[[str, str, str], float]
@dataclass(frozen=True)
class TimingPolicy:
policy_version: str
policy_sha256: str
trials: int
bootstrap_samples: int
confidence_quantile: float
max_material_delta_ms: float
analysis_seed: int
def valid_ttft(value: float) -> float:
if (
isinstance(value, bool)
or not isinstance(value, (int, float))
or not math.isfinite(float(value))
or float(value) < 0
):
raise ValueError("invalid_gateway_ttft")
return float(value)
def upper_quantile(values: list[float], policy: TimingPolicy) -> float:
rng = random.Random(policy.analysis_seed)
estimates = sorted(
statistics.median(rng.choices(values, k=len(values)))
for _ in range(policy.bootstrap_samples)
)
return estimates[math.ceil(policy.confidence_quantile * len(estimates)) - 1]
def run_probe(
measure: MeasureTTFT,
victim_tenant: str,
observer_tenant: str,
policy: TimingPolicy,
) -> dict:
if victim_tenant == observer_tenant:
raise ValueError("distinct_tenants_required")
if (
not policy.policy_version
or len(policy.policy_sha256) != 64
or policy.trials < 2
or policy.bootstrap_samples < 1
or not 0.5 < policy.confidence_quantile < 1.0
or not math.isfinite(policy.max_material_delta_ms)
or policy.max_material_delta_ms < 0
):
raise ValueError("measurement_policy_invalid")
order = random.Random(policy.analysis_seed)
deltas: list[float] = []
run_nonce = secrets.token_hex(16)
suffix = "\nSummarize this synthetic public notice in one sentence."
for index in range(policy.trials):
treatment = secrets.token_urlsafe(32) + suffix
control = secrets.token_urlsafe(32) + suffix
valid_ttft(measure(victim_tenant, treatment, f"{run_nonce}-{index}-prime"))
pair = [("treatment", treatment), ("control", control)]
order.shuffle(pair)
observed = {
label: valid_ttft(measure(
observer_tenant, prompt, f"{run_nonce}-{index}-{label}"
))
for label, prompt in pair
}
deltas.append(observed["control"] - observed["treatment"])
upper = upper_quantile(deltas, policy)
return {
"schema_version": "aidefend.h027-cache-timing.v1",
"outcome": "PASS" if upper <= policy.max_material_delta_ms else "FAIL",
"policy": asdict(policy),
"run_nonce": run_nonce,
"completed_trials": len(deltas),
"median_delta_ms": statistics.median(deltas),
"upper_delta_ms": upper,
"paired_deltas_ms": deltas,
}Evidence and independent replay
Disable response and semantic caches while leaving only the prefix/KV mechanism under test. Pin both identities to the same approved route and capture gateway TTFT, not completion time. Sign the result and retain route, queue, engine, tokenizer, model, cache, hardware class, policy digest, request receipts, and measurement IDs. A separate job verifies the signature, reconciles exactly the policy population, recomputes all deltas and bootstrap output, and repeats a reuse-disabled negative control.
AID-H-027-G004
Apply this read gate before every cached value is reused. Redis metadata and TTL share the value's compromise domain and cannot authenticate it. MAC the canonical metadata and value digest with a key outside Redis, include a key ID and policy expiry, and distinguish a clean miss from tamper denial and infrastructure error.
Action: store and verify an authenticated cache envelope
# File: cache/authenticated_envelope.py
from __future__ import annotations
import hashlib
import hmac
import json
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Callable
import redis
META_FIELDS = {
"schema_version", "key_id", "writer_request_id", "context_sha256",
"model_release", "policy_sha256", "value_sha256", "created_at", "expires_at",
}
@dataclass(frozen=True)
class ReadDecision:
outcome: str
decision: str
reason: str
value: bytes | None
metadata: dict | None
def canonical(value: dict) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
+ "\n"
).encode("utf-8")
def utc(value: str) -> datetime:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.tzinfo is None or parsed.utcoffset() != timedelta(0):
raise ValueError("timestamp_must_be_utc")
return parsed
def mac(key: bytes, metadata_bytes: bytes) -> bytes:
if len(key) < 32:
raise ValueError("mac_key_too_short")
return hmac.new(key, metadata_bytes, hashlib.sha256).hexdigest().encode("ascii")
def store(
client: redis.Redis, full_key: str, value: bytes, *, key_id: str,
key: bytes, writer_request_id: str, context_sha256: str,
model_release: str, policy_sha256: str, ttl_seconds: int, now: datetime,
) -> dict:
if now.tzinfo is None or now.utcoffset() != timedelta(0):
raise ValueError("now_must_be_utc")
if isinstance(ttl_seconds, bool) or not isinstance(ttl_seconds, int) or ttl_seconds < 1:
raise ValueError("ttl_invalid")
created = now.astimezone(timezone.utc)
metadata = {
"schema_version": "aidefend.h027-cache-envelope.v1",
"key_id": key_id,
"writer_request_id": writer_request_id,
"context_sha256": context_sha256,
"model_release": model_release,
"policy_sha256": policy_sha256,
"value_sha256": hashlib.sha256(value).hexdigest(),
"created_at": created.isoformat().replace("+00:00", "Z"),
"expires_at": (created + timedelta(seconds=ttl_seconds)).isoformat().replace("+00:00", "Z"),
}
metadata_bytes = canonical(metadata)
with client.pipeline(transaction=True) as transaction:
transaction.hset(full_key, mapping={
"value": value, "metadata": metadata_bytes,
"metadata_mac": mac(key, metadata_bytes),
})
transaction.expire(full_key, ttl_seconds)
transaction.execute()
return metadata
def load(
client: redis.Redis, full_key: str, *, expected: dict,
resolve_key: Callable[[str], bytes], now: datetime,
) -> ReadDecision:
record = client.hgetall(full_key)
if not record:
return ReadDecision("PASS", "MISS", "entry_absent", None, None)
def field(name: str) -> bytes | None:
return record.get(name.encode("utf-8"), record.get(name))
value, metadata_bytes, supplied_mac = (
field("value"), field("metadata"), field("metadata_mac")
)
if not all(isinstance(item, bytes) for item in (value, metadata_bytes, supplied_mac)):
client.delete(full_key)
return ReadDecision("FAIL", "DENY", "envelope_incomplete", None, None)
try:
metadata = json.loads(metadata_bytes)
if not isinstance(metadata, dict) or set(metadata) != META_FIELDS:
raise ValueError("metadata_schema_invalid")
if not hmac.compare_digest(
supplied_mac, mac(resolve_key(metadata["key_id"]), metadata_bytes)
):
raise ValueError("metadata_mac_invalid")
if canonical(metadata) != metadata_bytes:
raise ValueError("metadata_not_canonical")
if hashlib.sha256(value).hexdigest() != metadata["value_sha256"]:
raise ValueError("value_digest_mismatch")
if any(metadata.get(name) != wanted for name, wanted in expected.items()):
raise ValueError("expected_context_mismatch")
if utc(metadata["expires_at"]) <= now.astimezone(timezone.utc):
raise ValueError("entry_expired")
except (KeyError, TypeError, ValueError, json.JSONDecodeError, UnicodeDecodeError) as exc:
client.delete(full_key)
return ReadDecision("FAIL", "DENY", str(exc), None, None)
return ReadDecision("PASS", "HIT", "authenticated_envelope", value, metadata)Enforcement, evidence, and outcomes
The gateway reuses only a PASS/HIT; a clean miss generates fresh output. Resolve historical keys by key ID from KMS and use dual-read/single-write rotation. Sign a receipt for every hit, miss, and denial, binding request, cache-key digest, expected context, metadata digest, key ID, decision, and response. Unexpected Redis/KMS/serialization exceptions are ERROR, never a miss.
AID-H-027-G005
Apply whenever a cache-key dependency changes. Do not use SCAN plus deletion as the security boundary; concurrent writers and incomplete scans can leave stale entries reachable. Include an atomic tenant/security-context generation in every cache namespace and advance it before the new model, prompt, tool policy, or corpus release receives traffic.
Action: advance an idempotent generation atomically
# File: cache/generation_invalidation.py
from __future__ import annotations
import re
from dataclasses import dataclass
import redis
SHA256 = re.compile(r"^[a-f0-9]{64}$")
CHANGE_TYPES = {"model_release", "system_prompt", "tool_policy", "corpus_release"}
ADVANCE_LUA = r"""
local prior = redis.call('HGET', KEYS[2], ARGV[3])
if prior then return {2, prior} end
local current = redis.call('GET', KEYS[1])
if not current then current = '1' end
if current ~= ARGV[1] then return {0, current} end
redis.call('SET', KEYS[1], ARGV[2])
redis.call('HSET', KEYS[2], ARGV[3], ARGV[2])
redis.call(
'XADD', KEYS[3], '*', 'change_id', ARGV[3],
'change_type', ARGV[4], 'generation', ARGV[2],
'release_tuple_sha256', ARGV[5]
)
return {1, ARGV[2]}
"""
@dataclass(frozen=True)
class AdvanceResult:
outcome: str
reason: str
generation: int
change_id: str
release_tuple_sha256: str
def advance_generation(
client: redis.Redis, partition_sha256: str, *, expected_generation: int,
change_id: str, change_type: str, release_tuple_sha256: str,
) -> AdvanceResult:
if not SHA256.fullmatch(partition_sha256):
raise ValueError("partition_sha256_invalid")
if (
isinstance(expected_generation, bool)
or not isinstance(expected_generation, int)
or expected_generation < 1
):
raise ValueError("expected_generation_invalid")
if not change_id or change_type not in CHANGE_TYPES:
raise ValueError("change_identity_invalid")
if not SHA256.fullmatch(release_tuple_sha256):
raise ValueError("release_tuple_sha256_invalid")
next_generation = expected_generation + 1
prefix = f"h027:{partition_sha256}"
raw = client.eval(
ADVANCE_LUA, 3, f"{prefix}:generation", f"{prefix}:changes",
f"{prefix}:events", str(expected_generation), str(next_generation),
change_id, change_type, release_tuple_sha256,
)
status, actual = int(raw[0]), int(raw[1])
if status == 0:
return AdvanceResult(
"FAIL", "generation_compare_failed", actual,
change_id, release_tuple_sha256,
)
return AdvanceResult(
"PASS", "idempotent_replay" if status == 2 else "generation_advanced",
actual, change_id, release_tuple_sha256,
)Deployment order, evidence, and replay
The controller verifies the signed change manifest, advances the generation, independently reads it, waits for every in-scope gateway to attest the same value, and only then shifts traffic. Old objects expire asynchronously but are unreachable. Sign and retain manifest/release/partition/script digests, change ID, prior/new generation, controller receipt, complete gateway inventory, per-instance readback, traffic-shift receipt, and an old-generation miss probe.
AID-H-027-G006
Apply at the gateway and invalidation controller for every lookup, write, denial, and generation advance. Do not log prompts, outputs, raw tenant/user IDs, credentials, or reusable keys. A response header is operational metadata, not assurance evidence; the signed event and sink readback are authoritative.
Action: emit a canonical signed event and verify readback
# File: cache/provenance_event.py
from __future__ import annotations
import hashlib
import hmac
import json
import re
from datetime import datetime, timedelta
from typing import Callable
SHA256 = re.compile(r"^[a-f0-9]{64}$")
EVENT_TYPES = {
"cache_hit", "cache_miss", "cache_write",
"cache_deny", "cache_generation_advance",
}
Sign = Callable[[bytes], bytes]
Write = Callable[[str, bytes, bytes], None]
Read = Callable[[str], tuple[bytes, bytes]]
def canonical(value: dict) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
+ "\n"
).encode("utf-8")
def pseudonym(value: str, audit_key: bytes) -> str:
if not value or len(audit_key) < 32:
raise ValueError("pseudonym_input_invalid")
return hmac.new(audit_key, value.encode(), hashlib.sha256).hexdigest()
def emit_cache_event(
*, event_id: str, occurred_at: str, event_type: str, request_id: str,
tenant_id: str, subject_id: str, cache_key_sha256: str,
context_sha256: str, generation: int, model_release: str,
policy_sha256: str, writer_request_id: str | None, audit_key: bytes,
sign: Sign, write: Write, read: Read,
) -> dict:
timestamp = datetime.fromisoformat(occurred_at.replace("Z", "+00:00"))
if timestamp.tzinfo is None or timestamp.utcoffset() != timedelta(0):
raise ValueError("occurred_at_must_be_utc")
if event_type not in EVENT_TYPES or not event_id or not request_id:
raise ValueError("event_identity_invalid")
if not all(SHA256.fullmatch(value) for value in (
cache_key_sha256, context_sha256, policy_sha256,
)):
raise ValueError("digest_invalid")
if isinstance(generation, bool) or not isinstance(generation, int) or generation < 1:
raise ValueError("generation_invalid")
event = {
"schema_version": "aidefend.h027-cache-provenance.v1",
"event_id": event_id,
"occurred_at": timestamp.isoformat().replace("+00:00", "Z"),
"event_type": event_type,
"request_id": request_id,
"tenant_pseudonym": pseudonym(tenant_id, audit_key),
"subject_pseudonym": pseudonym(subject_id, audit_key),
"cache_key_sha256": cache_key_sha256,
"context_sha256": context_sha256,
"generation": generation,
"model_release": model_release,
"policy_sha256": policy_sha256,
"writer_request_id": writer_request_id,
}
payload, signature = canonical(event), sign(canonical(event))
if not signature:
raise RuntimeError("empty_evidence_signature")
write(event_id, payload, signature)
stored_payload, stored_signature = read(event_id)
if not hmac.compare_digest(stored_payload, payload):
raise RuntimeError("sink_payload_readback_mismatch")
if not hmac.compare_digest(stored_signature, signature):
raise RuntimeError("sink_signature_readback_mismatch")
return {
"event_id": event_id,
"event_sha256": hashlib.sha256(payload).hexdigest(),
"signature_sha256": hashlib.sha256(signature).hexdigest(),
"outcome": "PASS",
}Population verification and outcomes
Block response completion until the event is durably acknowledged, or deny under a signed fail-closed availability policy. The verifier joins the complete response population to events, verifies historical trust, requires exactly one lookup decision per response, traces each hit to its writer and current generation, and reconciles generation advances separately.
Open-source tools
- Valkey
- Redis 8+ (AGPL-3.0)
- LangChain
- vLLM
- OpenTelemetry Collector (cache provenance event export)
- Open Policy Agent (OPA)
Commercial / hosted tools
- Redis Enterprise (active-active with ACLs)
- Amazon ElastiCache
- Google Cloud Memorystore
- Azure Managed Redis
Threat mappings
MITRE ATLAS
- AML.T0057 LLM Data Leakage (context-bound cache keys prevent other users' cached outputs from crossing identity boundaries)
MAESTRO
- Data Tampering (L2)
- Data Leakage (Cross-Layer) (cross-tenant cache reuse leaks responses across boundaries)
OWASP LLM Top 10 2026
- LLM02:2026 Sensitive Information Disclosure
OWASP ML Top 10 2023
- ML09:2023 Output Integrity Attack (authenticated cache envelopes prevent forged outputs from being reused)
OWASP Top 10 for Agentic Applications 2026
- ASI06:2026 Memory & Context Poisoning
- ASI03:2026 Identity and Privilege Abuse (cache reuse across privilege boundaries)
NIST Adversarial Machine Learning 2025
- N/A (the catalog has no specific inference-cache poisoning or cross-tenant cache-reuse attack)
Cisco Integrated AI Security and Safety Framework
- AISubtech-4.2.2 Session Boundary Violation
- AITech-4.2 Context Boundary Attacks
- AITech-8.2 Data Exfiltration / Exposure
- AITech-8.3 Information Disclosure
Google Secure AI Framework 2.0 - Risks
- SDD: Sensitive Data Disclosure (cross-tenant cache reuse leaks responses across trust boundaries)
- EDH: Excessive Data Handling (generation and expiry policy bound cached-response retention)
Databricks AI Security Framework 3.0
- Model Serving - Inference response 10.6: Sensitive data output from a model (context-bound caches prevent an authorized output from being served to another tenant)