Runtime Security

Runtime Security for AI Agents: Why Static Guardrails Are Not Enough

July 2026 · 15 min read · AI Runtime Security
STATIC GUARDRAILS configured at deploy time System prompt instructions can be overridden by injection Network ACLs / firewall rules blind to local models + encrypted content cannot see session context · no tool-call visibility RUNTIME ENFORCEMENT evaluated at call time, every call In-process policy engine sees full prompt + session state + tool args Action-layer enforcement blocks before execution, not after detection covers cloud + local models · sub-millisecond · no proxy VS Static guardrails fail when content or context changes at runtime. Runtime enforcement doesn't.
Static vs runtime: static guardrails are configured at deploy time and cannot adapt to runtime conditions. Runtime enforcement evaluates every call with full context, session state, and tool visibility.

Why Static Guardrails Fail

A system prompt set at deploy time doesn't know that the agent just read a poisoned document three calls ago. Runtime enforcement does. Every AI agent deployment starts with static guardrails: a system prompt, model settings, maybe network firewall rules. These feel comprehensive at deploy time. They leave wide gaps in practice.

System prompts can be overridden. Prompt injection — whether from user input or content the agent reads — instructs the model to ignore the system prompt. The prompt is just text in the context window; it has no cryptographic authority over what the model does next.

Network controls are blind to content. A firewall can block evil.com but sees only HTTP headers, not the LLM's reasoning. A call to api.openai.com/v1/chat/completions looks identical whether it's a legitimate product query or a prompt-injected exfiltration attempt. Firewalls also have zero visibility into local models — Ollama, vLLM, llama.cpp — because those never make an outbound network call.

Static configuration can't see session state. Whether a tool call is safe depends on what happened earlier in the session: did the agent read an untrusted document? Has it accessed private customer data? Static configuration was set before the session began; it cannot answer those questions.

Policy changes require a redeploy. If you discover at 2am that a model is being misused, a static configuration means waiting for a deployment cycle — minutes to hours — before the block takes effect across running agents.

What Runtime Security Actually Means

Runtime security for AI agents means enforcement at the moment of each call — not at deploy time, not at network egress, but at the instant the agent invokes an LLM or a tool. A runtime security layer has access to four things a static system cannot have: the full prompt as assembled including all retrieved context and injected content, the complete session history, the tool name and arguments, and a live-refreshed policy bundle updatable without redeployment.

This architecture must be in-process. A proxy-based system routes agent traffic through a sidecar or gateway, adding network latency to every call and creating a dependency agents route around when it becomes inconvenient. An in-process runtime runs in the same memory space as the agent with zero-copy access to all call context, and covers every call regardless of which model or network path it uses.

Local-First Policy Enforcement

The key design question is where the enforcement decision happens. A naive implementation calls the policy server on every LLM call — adding a network round-trip to every inference request, adding latency and an availability dependency. A better design is local-first: the policy bundle is cached in-process and enforcement decisions happen locally, with no network call except for the one check that genuinely requires server state.

InferenceFort's enforcement model:

inferencefort — local-first enforcement with policy bundle
import inferencefort
from langchain_anthropic import ChatAnthropic

# engine.init() runs at import time:
# 1. Fetches GET /v1/policy → caches bundle in process memory
# 2. Starts daemon thread that refreshes every ttl_seconds (default 300)
# 3. Patches BaseChatModel, LiteLLM, BaseTool, MCP ClientSession

inferencefort.set_context(
    user="priya@corp.com",
    agent_id="research-assistant",
    thread_id="sess_7c4e"
)

llm = ChatAnthropic(model="claude-sonnet-4-5")

# Each invoke() call:
# enforce() → local policy check (~0.2ms, no network)
# → if budget cap: POST /v1/budget (~15ms round-trip)
# → call LLM (the actual inference)
# → record() → async POST /v1/ingest (fire-and-forget)
response = llm.invoke("What are the key findings from the 2025 report?")

Policy changes propagate within one TTL cycle — at most 5 minutes — with no deployment required. If you discover at 2am that a model is being misused, you block it in the dashboard and it's blocked across all running agent processes within 5 minutes, without touching deployment infrastructure.

Dynamic Tool Control

In a static system, blocking a tool means removing it from the agent's tool list and redeploying. In a runtime system, you update the policy bundle and the change propagates to every running agent within the TTL window. Tool policies have three states at the call layer:

Policies use glob patterns: delete_* blocks all delete-prefixed tools, *_external requires approval for anything ending in _external. One rule covers a category; your policy stays concise as the tool catalog grows.

Token Monitoring and Budget Enforcement

Token costs are a security signal, not just a billing issue. In 2025, a well-documented incident pattern emerged: a coding agent's retry logic on a transient API error triggered a runaway loop that consumed $40,000 of inference budget overnight before anyone noticed. With a daily budget cap set at $500/day per agent, that loop would have been blocked after the first few hundred dollars — at 2am, automatically, with no on-call page required.

Anomalously high token consumption is also the first observable symptom of a prompt injection attack that's triggered a loop, or a model-jacking attack using your inference budget for attacker-controlled tasks. Runtime budget enforcement has two components:

Per-call cost estimation (pre-call gate)

Before the LLM call executes, the SDK estimates cost using the tenant's pricing configuration (tokens in context × input price + estimated output tokens × output price). If the estimate would push the session or team over a daily cap, the call is blocked with a PolicyViolation before it reaches the model — no tokens consumed, no cost incurred.

Server-authoritative daily budget

Daily spend is shared across all processes serving a team — no single process has authoritative knowledge of the current total. When a policy includes a max_daily_usd cap, the SDK calls POST /v1/budget for a server-authoritative verdict. This is the only enforcement check requiring a network call, and it only fires for calls matching a budget-capped policy.

Why local budget isn't enough: An agent running as 100 parallel processes has a local view of at most 1/100th of real spend. A $100/day team cap with 100 processes means each process's local estimate allows $100 before it detects a problem — for a potential $10,000 actual spend. Daily caps require a server-authoritative check.

Human Approval Gates

The right model for high-risk agent actions is not "always allow" or "always block" — it's "pause and ask a human." Runtime approval gates implement this at the tool-call layer: when a tool call matches an approval policy, the call is held and a notification goes to a designated reviewer via dashboard or webhook (Slack, PagerDuty). The reviewer approves or denies; the agent resumes or raises a PolicyViolation.

Gate any action that is:

approval-required tool call — agent behavior
from inferencefort import PolicyViolation

# Policy: send_email has tool_action = "approval_required"
# When the agent calls send_email, instead of executing:
# 1. SDK emits audit event with verdict="approval_required"
# 2. Dashboard shows pending approval notification
# 3. If reviewer denies: PolicyViolation raised here
# 4. If reviewer approves: tool call proceeds

try:
    tool_result = send_email.run({
        "to": "client@enterprise.com",
        "subject": "Contract renewal proposal",
        "body": draft_body
    })
except PolicyViolation as e:
    # Reviewer denied, or timeout waiting for approval
    agent_response = "Email draft prepared but requires approval before sending."

Audit Trail: Every Call a Structured Record

A runtime security layer is only as valuable as the audit trail it produces. Every LLM call and every tool call should generate a structured event with enough information to reconstruct what happened, who authorized it, what data was involved, and what the outcome was. The fields that matter:

Field Type Why it matters
trace_id UUID Links all spans in a multi-step agent session
user / agent_id string Attribution — who was responsible for this call
model string Which model was called — policy compliance evidence
provider + resolved_endpoint string Egress compliance — where the call actually went
verdict enum allow / block / redact / approval_required
rule_id string Which policy rule triggered a block/redact
input_tokens / output_tokens int Cost accounting + anomaly detection
cost_usd float Per-call and cumulative cost tracking
local_decision bool Distinguishes SDK enforcement from server backstop
latency_ms int Performance baseline; spikes warrant investigation

Runtime DLP: Redaction Before the LLM Sees It

Data loss prevention at the runtime layer means intercepting sensitive data before it leaves the process — including before it's sent to a cloud LLM. The enforcement order matters: content rules run first on the original text (so they can detect sensitive patterns and record what was found), then redaction runs before the call executes. The LLM only ever sees the sanitized version.

For HIPAA-regulated workloads, the PHI detector (backed by Microsoft Presidio) identifies 18 HIPAA Safe Harbor identifier categories — names, dates, geographic subdivisions, phone numbers, SSNs, medical record numbers, device identifiers, and more — and replaces them with generic placeholders before the prompt reaches any cloud model. A patient record entering the agent as "John Smith, DOB 1978-03-15, SSN 123-45-6789" exits the redaction layer as "[PERSON], DOB [DATE], SSN [SSN]". The LLM can still reason about the record structure; it never sees the actual PHI.

This matters for two reasons: compliance (PHI cannot be transmitted to a processor without a signed BAA) and security (if the LLM's responses are intercepted or logged by the provider, the actual PHI was never in the payload).

Runtime Architecture

AGENT PROCESS BOUNDARY AGENT CODE LangChain / LlamaIndex AutoGen / custom INFERENCEFORT SDK INTERCEPT _enforce() pre-call _record() post-call DLP redact · budget check cost WAL · async ingest POLICY BUNDLE CACHE models · content rules · egress MCP allow-list · tool policy refreshed every 300s via ETag SESSION STATE (LRU) trifecta caps per (user, thread) trace_id · step counter max 10k sessions in memory CONTROL PLANE policy bundle · budget audit events LLM / TOOLS only if policy allows
Runtime architecture: all enforcement happens inside the agent process. The policy cache provides sub-millisecond local decisions; the control plane is only called for budget checks and async audit recording.

Implementation Checklist

Secure your AI agents today.

InferenceFort enforces policy on every LLM call and tool call — in-process, before it runs.

Get early access →