AID-H-017.004: Transient & Isolated State Management

Version 1.20261015 · Actionable sub-technique

Design an agent's application-level working state to avoid unnecessary cross-task persistence: keep task handling stateless where practical, construct request-local state from the current authenticated task, and reload signed mission objectives from a trusted source. When a deployment retains volatile context in RAM, Redis, or another shared store, consume the runtime isolation, size, TTL, reset, and serialization evidence owned by AID-I-004.001 rather than scoring those store controls here.

Pillars: app

Lifecycle: building, operation

Scope boundary

Owns application-level statelessness, task-local state construction, cross-task persistence avoidance, and trusted mission reload. It does not own runtime context-store tenant or session isolation, size limits, TTL, reset, serialization, process teardown, or memory zeroization.

AID-I-004.001: AID-H-017.004 owns the application architecture that avoids or minimizes retained cross-task state and reloads the trusted mission for each task; AID-I-004.001 owns steady-state runtime enforcement for any volatile context store, including tenant and session isolation, bounded size, TTL, resets, and fail-closed serialization. A stateless design can consume AID-I-004.001 evidence when a store exists, but retained-state TTL and isolation receive only the AID-I-004.001 outcome.

AID-I-001: AID-H-017.004 controls application-level state ownership and lifetime; AID-I-001 controls process, container, sandbox, filesystem, and runtime teardown or zeroization. Application expiry does not prove low-level execution residue was removed.

Implementation guidance

AID-H-017.004-G001

Implement stateless agents with ephemeral memory for each task.
Concept:

An agent's memory can be a vector for persistent attacks. A secure architecture treats an agent's short-term memory as ephemeral and untrusted by default. The agent should be designed to be as stateless as possible, reloading its core, signed objective from a trusted source at the beginning of each new task or session.

Implement a Stateless Agent with Ephemeral Memory

This design pattern ensures that each new task starts with a clean slate, purging any potential manipulation from previous conversations.

# File: agent_arch/stateless_handler.py
from __future__ import annotations
import uuid
import json
import math
import ssl
from urllib.parse import urlsplit, urlunsplit

import base64
import os

import httpx
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

def _required_positive_int(name: str) -> int:
    value = int(os.environ[name])
    if value <= 0:
        raise RuntimeError(f"{name} must be a positive integer")
    return value

TASK_INPUT_MAX_UTF8_BYTES = _required_positive_int("VERIFIED_H017_TASK_INPUT_MAX_UTF8_BYTES")


# aidefend.stateless-task-request.v1 requires a UUID request ID and non-empty input.
NON_EMPTY_TASK_INPUT_LENGTH = 1

def _required_positive_seconds(name: str) -> float:
    value = float(os.environ[name])
    if not math.isfinite(value) or value <= 0:
        raise RuntimeError(f"{name} must be a finite positive number of seconds")
    return value

STATELESS_WORKER_TOTAL_TIMEOUT_SECONDS = _required_positive_seconds("VERIFIED_H017_STATELESS_WORKER_TOTAL_TIMEOUT_SECONDS")
STATELESS_WORKER_CONNECT_TIMEOUT_SECONDS = _required_positive_seconds("VERIFIED_H017_STATELESS_WORKER_CONNECT_TIMEOUT_SECONDS")
STATELESS_WORKER_RESPONSE_MAX_BYTES = _required_positive_int("VERIFIED_H017_STATELESS_WORKER_RESPONSE_MAX_BYTES")


app = FastAPI()
MODEL_GATEWAY_URL = os.environ["MODEL_GATEWAY_URL"]
_gateway = urlsplit(MODEL_GATEWAY_URL)
if (
    _gateway.scheme != "https" or not _gateway.hostname
    or _gateway.username is not None or _gateway.password is not None
    or _gateway.query or _gateway.fragment or not _gateway.path.startswith("/")
    or MODEL_GATEWAY_URL != urlunsplit(("https", _gateway.netloc.lower(), _gateway.path, "", ""))
):
    raise RuntimeError("model gateway URL must be the exact canonical HTTPS endpoint")
MODEL_ID = os.environ["MODEL_ID"]
SYSTEM_PROMPT = os.environ["TRUSTED_SYSTEM_PROMPT"]
GOAL_BYTES = base64.b64decode(os.environ["SIGNED_GOAL_B64"], validate=True)
GOAL_SIGNATURE = base64.b64decode(os.environ["SIGNED_GOAL_SIGNATURE_B64"], validate=True)
GOAL_PUBLIC_KEY = Ed25519PublicKey.from_public_bytes(
    base64.b64decode(os.environ["GOAL_VERIFY_KEY_B64"], validate=True)
)


class TaskRequest(BaseModel):
    request_id: uuid.UUID
    input: str = Field(min_length=NON_EMPTY_TASK_INPUT_LENGTH)


def verified_goal() -> str:
    try:
        GOAL_PUBLIC_KEY.verify(GOAL_SIGNATURE, GOAL_BYTES)
    except InvalidSignature as exc:
        raise RuntimeError("signed goal verification failed") from exc
    return GOAL_BYTES.decode("utf-8")


@app.post("/new_task")
def handle_new_task(request: TaskRequest) -> dict:
    if len(request.input.encode("utf-8")) > TASK_INPUT_MAX_UTF8_BYTES:
        raise HTTPException(status_code=413, detail="task input exceeds signed-policy byte limit")
    try:
        # The task-local list is the complete memory for this request. Nothing is
        # loaded from, or written to, a cross-session conversation store.
        messages = [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "developer", "content": verified_goal()},
            {"role": "user", "content": request.input},
        ]
        context = ssl.create_default_context(cafile=os.environ["VERIFIED_H017_MODEL_GATEWAY_CA_BUNDLE"])
        context.load_cert_chain(
            os.environ["VERIFIED_H017_MODEL_GATEWAY_CLIENT_CERT"],
            os.environ["VERIFIED_H017_MODEL_GATEWAY_CLIENT_KEY"],
        )
        with httpx.Client(
            verify=context, follow_redirects=False,
            timeout=httpx.Timeout(STATELESS_WORKER_TOTAL_TIMEOUT_SECONDS, connect=STATELESS_WORKER_CONNECT_TIMEOUT_SECONDS),
        ) as client:
            with client.stream(
                "POST", MODEL_GATEWAY_URL,
                json={"model": MODEL_ID, "messages": messages},
                headers={"X-Request-ID": str(request.request_id), "Accept": "application/json"},
            ) as response:
                if response.status_code != 200:
                    raise RuntimeError(f"model gateway returned HTTP {response.status_code}")
                raw = bytearray()
                for chunk in response.iter_bytes():
                    raw.extend(chunk)
                    if len(raw) > STATELESS_WORKER_RESPONSE_MAX_BYTES:
                        raise RuntimeError("model gateway response exceeds signed decoded-byte cap")
            payload = json.loads(bytes(raw))
            if not isinstance(payload, dict) or set(payload) != {"output"} or not isinstance(payload["output"], str):
                raise ValueError("model gateway response schema differs")
        return {"request_id": str(request.request_id), "result": payload["output"]}
    except (RuntimeError, httpx.HTTPError, KeyError, ValueError) as exc:
        raise HTTPException(status_code=503, detail="task execution unavailable") from exc
    finally:
        if "messages" in locals():
            messages.clear()
Verification and evidence

Run two sessions with distinct canary strings and prove neither canary appears in the other request, logs, cache, trace attributes, or subsequent model input. Tamper with the goal bytes and require fail-closed behavior before the gateway call. Preserve the model and prompt versions, signed-goal digest and signer identity, request/trace IDs, outbound message-role readback with content redacted, storage/cache query results, and explicit PASS/FAIL/ERROR verdict. Clearing a Python list is lifecycle hygiene, not guaranteed memory zeroization; highly sensitive tasks still require the process or sandbox teardown owned by AID-I-001.003.

Action: Keep this handler stateless: construct request-local state from the authenticated task, reload the trusted signed prompt and objective, and discard that state when the task ends. Do not create or score a shared context store here. If the architecture retains context in RAM, Redis, or another shared store, consume the tenant/session isolation, size, TTL, reset, and serialization evidence owned by AID-I-004.001.

Keep the action safely bounded

Reconcile the complete population of every eligible task, its request-local state objects, temporary credentials, and teardown events; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.

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-017.004: Transient & Isolated State Management | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version