AID-H-027: Inference Cache Integrity, Partitioning & Safe Reuse

Version 1.20261015 · Actionable technique

Protect inference cache objects, keys, metadata, namespaces, and invalidation generations against poisoning, cross-boundary contamination, unsafe reuse, and cache-layer timing exposure. Bind every lookup and write to the exact tenant, principal, model, prompt, corpus, tool policy, and security-context generation; authenticate stored envelopes and fail closed on tamper.

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.

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

Partition cache objects and cache reuse keys by tenant and security context, and hand serving-topology isolation decisions to the serving-state control.

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

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.

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

Fail closed on cache reuse when provenance or isolation metadata is missing or unverifiable.

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

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.

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

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.

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

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-027: Inference Cache Integrity, Partitioning & Safe Reuse | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version