VaikoraVaikora

VaikoraBlog › Developer Guides

LangChain Agent Security: Runtime Controls Guide | Vaikora

Developer Guides · June 30, 2026 · 11 min read

LangChain agent security means enforcing deterministic policy on every LLM call and tool invocation before execution happens. The core insight: agents are chains of reasoning plus side effects (database writes, API calls, file access). You need visibility and control at runtime, not just prompt guardrails. Runtime policy enforcement, checking every action against a security policy in under a second, blocks prompt injection, redacts PII, and prevents unauthorized tool use without rewriting your agent logic.

The Security Gap in LangChain Agents

LangChain agents are powerful because they can reason and act: they receive a task, call an LLM to think, invoke tools to gather data, and repeat until they reach a goal. This autonomy is also the security risk.

A LangChain agent running without runtime controls faces three immediate threats. First, prompt injection: an attacker embeds a malicious instruction in data the agent retrieves, and the agent treats it as a legitimate goal and executes it. Second, unauthorized tool access: the agent's LLM might invent or misuse a tool call (e.g., calling a deletion function when only reads are allowed). Third, data exfiltration: the agent collects sensitive data during reasoning and sends it back to the user or logs it unredacted.

Prompt guardrails (jailbreak detection, output validation) happen after the LLM has already decided what to do. Runtime policy enforcement happens before execution. You intercept the LLM's proposed action, check it against a policy, and return ALLOW, LOG, CONSTRAIN, or BLOCK in under a second. The agent never sees the blocked action.

Why Existing Approaches Fall Short

Many teams start with input validation and logging, then add jailbreak filters to the prompt. These help, but they miss the middle: the moment the LLM commits to an action.

Custom middleware that validates tool calls is better. But writing and maintaining role-based access control, PII detection, and policy-decision logic inside your application code is slow and error-prone. Every policy change requires a code deploy. Every new threat category requires new validation logic.

OWASP's LLM Top 10 and the AI Risk Management Framework (NIST AI RMF) both emphasize the need for runtime guardrails and audit trails. Autonomous tool invocation without controls is a high-risk surface. The answer is a dedicated policy layer outside your agent code.

How Runtime Policy Enforcement Works

A runtime policy engine sits between your agent and the LLM, and between the LLM and your tools. When your LangChain agent calls an LLM, the request flows through the policy engine. The engine checks the prompt, the model choice, and any additional context against a defined policy. It returns the LLM response. When the LLM proposes a tool call, the engine validates the call: Does the user have permission? Is the input safe? Does the call expose sensitive data?

The policy engine makes four decisions: - ALLOW: execute as-is. - LOG: allow and log for audit. - CONSTRAIN: modify the action (e.g., redact PII) and allow. - BLOCK: reject the action entirely.

Each decision is signed and appended to an audit chain, creating a tamper-evident record of every choice the system made and why.

Securing LangChain with an OpenAI-Compatible Gateway

The simplest way to add runtime security to LangChain is to redirect its LLM calls through a policy gateway. LangChain's ChatOpenAI class accepts a base_url parameter. Point it at a policy gateway instead of OpenAI's API, and every LLM call is now inspected.

Here's a working example:

from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import tool

# Point LangChain at your policy gateway
llm = ChatOpenAI(
    model="gpt-4",
    base_url="http://localhost:8000/v1",  # Your policy gateway
    api_key="your-vaikora-gateway-key",
    temperature=0.7,
)

@tool
def fetch_user_data(user_id: str):
    """Fetch user profile and order history."""
    # Simulated data retrieval
    return {
        "user_id": user_id,
        "name": "Alice Smith",
        "email": "alice@example.com",
        "orders": [{"id": 1001, "amount": 250}],
    }

@tool
def send_email(recipient: str, subject: str, body: str):
    """Send an email to the recipient."""
    return f"Email sent to {recipient}"

tools = [fetch_user_data, send_email]

# Create and run the agent
agent = create_react_agent(llm, tools)
executor = AgentExecutor.from_agent_and_tools(agent, tools, verbose=True)

# This call now flows through the policy gateway
result = executor.invoke({
    "input": "Fetch Alice's order history and send her a receipt."
})

Every LLM call and every tool invocation now passes through the policy gateway. The gateway checks: - Does the prompt contain signs of injection? - Has the user permission to invoke fetch_user_data? - Does the response contain unredacted PII? - Is the email recipient on a blocked list?

If a check fails, the gateway returns a constrained response (e.g., redacted email) or blocks the action entirely. LangChain sees a normal response and continues, but the policy layer has already protected you.

Defining Policies for LangChain Agents

Runtime policies need to be specific to your use case, but they share a structure. A policy typically includes:

Here's an example policy configuration:

policies:
  - name: "prevent_unauthorized_deletions"
    priority: critical
    triggers:
      - type: "tool_call"
        tool_name: "delete_user_data"
    condition: |
      user.role NOT IN ("admin", "data_protection_officer")
    action: "BLOCK"
    reason: "Only admins can delete user data"

  - name: "redact_pii_in_responses"
    priority: high
    triggers:
      - type: "llm_response"
    patterns:
      - pattern: '\b\d{3}-\d{2}-\d{4}\b'
        type: "ssn"
      - pattern: '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
        type: "email"
    action: "CONSTRAIN"
    constraint: "redact"

  - name: "log_data_access"
    priority: medium
    triggers:
      - type: "tool_call"
        tool_name: "fetch_user_data"
    action: "LOG"
    audit_fields:
      - "user_id"
      - "caller_role"
      - "timestamp"

  - name: "detect_prompt_injection"
    priority: critical
    triggers:
      - type: "llm_prompt"
    threat_detection:
      - type: "injection"
        keywords: ["ignore previous", "system override", "jailbreak"]
    action: "BLOCK"

This policy blocks deletions by non-admins, redacts PII in responses, logs all data access, and blocks obvious injection attempts. You deploy it once; the policy engine enforces it on every call.

Addressing Data Leakage in Agents

A common vulnerability: the LLM retrieves sensitive data during reasoning, and that data ends up in logs, responses, or intermediate API calls.

Runtime policy enforcement prevents this by inspecting data at three points: 1. Retrieval: when a tool returns data (e.g., fetch_user_data), the policy engine can redact sensitive fields before the LLM sees them. 2. Response: before the agent returns a result to the user, the policy engine scans for unredacted PII and removes or masks it. 3. Audit: all decisions are logged with redacted values, never raw secrets.

For example, if fetch_user_data returns an email address, the policy can replace it with [email redacted] before passing it to the LLM. The agent still knows a user exists and was contacted, but the LLM never sees the raw email, so it cannot accidentally include it in a log or send it to a third-party service.

Preventing Unauthorized Tool Calls

LLMs are remarkably good at reasoning, but they sometimes make mistakes or get confused by adversarial input. An attacker might ask the agent to "list all users and export their data to an external server." The LLM might decide that's a legitimate request and generate a tool call.

Runtime policy enforcement stops this by validating tool calls before execution:

# Policy: only admins can export data
if tool_name == "export_data" and user.role != "admin":
    return BLOCK("Data export restricted to admins")

# Policy: only internal IP addresses
if tool_name == "send_to_external_service":
    if not is_internal_ip(args["destination"]):
        return BLOCK("External calls blocked by policy")

# Policy: rate limiting
if tool_name in ["fetch_user_data", "send_email"]:
    if call_count(tool_name, user_id) > 10 per_minute:
        return CONSTRAIN("Rate limit exceeded; processing slower")

Each tool invocation is checked before execution. The agent never learns that a dangerous tool exists; it only sees allowed actions.

Compliance and Audit Trails

Regulated industries (healthcare, finance, insurance) require audit trails that prove who did what and when. A policy engine that signs every decision into an append-only chain creates that proof.

Each decision record includes: - The timestamp - The user and role - The LLM prompt and response - The tool call and result - The policy applied - The decision (ALLOW, LOG, CONSTRAIN, BLOCK) - A cryptographic signature

This audit chain is tamper-evident: you can prove that a record has not been modified since the decision was made. Regulators (GDPR, HIPAA, PCI DSS, SOC 2) all require this kind of evidence. Policy enforcement with signed audit records makes compliance demonstration straightforward without rebuilding audit infrastructure yourself.

Deploying Runtime Security in Three Steps

Step 1: Deploy the policy gateway. The Vaikora gateway (vaikora-llm-gateway) is MIT-licensed open-core, so you can self-host it on any infrastructure. It runs as a simple HTTP server and accepts OpenAI-compatible requests.

docker run -d \
  -p 8000:8000 \
  -e UPSTREAM_LLM_API_KEY=sk-... \
  vaikora/llm-gateway:latest

Step 2: Update your LangChain configuration. Point your ChatOpenAI instance at the gateway.

llm = ChatOpenAI(
    model="gpt-4",
    base_url="http://your-gateway:8000/v1",
    api_key="your-gateway-key",
)

Step 3: Define and deploy a policy. Write your policy (access controls, threat detection, data handling) and deploy it to the gateway. Policies are YAML files, no code changes needed.

The entire setup takes under an hour and requires no changes to your agent code. The policy layer sits transparently between your application and the LLM.

Tool Authorization and Role-Based Access

LangChain agents often have access to tools that should only be available to certain users or roles. Runtime policy enforcement enables fine-grained tool authorization.

For example, a multi-tenant SaaS might expose delete_subscription and issue_refund tools. A regular user should never invoke these; only a billing administrator should. A policy can encode this:

policies:
  - name: "restrict_destructive_tools"
    triggers:
      - type: "tool_call"
        tool_names: ["delete_subscription", "issue_refund", "export_all_data"]
    condition: |
      user.role != "admin" AND user.role != "billing_manager"
    action: "BLOCK"
    reason: "Destructive operations restricted to admins"

When a non-admin user's agent tries to invoke delete_subscription, the policy engine rejects the call. The user never learns the tool exists. The agent, seeing a "not authorized" response, moves on to other options or tells the user that the operation is not available.

Monitoring and Alerting

Runtime policy enforcement generates rich data for monitoring. Each policy decision is an event: you can track blocked requests, constrained actions, and policy violations in real time.

Set up alerts for: - Repeated injection attempts (suggests active attack) - Unusual tool access patterns (a user invoking tools they normally don't use) - High-confidence PII in responses (redaction failed somewhere) - Policy decisions by role and tool (for compliance reporting)

Most policy engines include a dashboard for visualizing these events. Built-in alerting and compliance reports make it straightforward to integrate with your existing security monitoring without custom logging code.

Integrating with LangChain's Tool Parsing

LangChain's create_react_agent automatically parses tool definitions and generates schemas. When your agent runs, LangChain passes tool schemas to the LLM so it knows what tools are available. Runtime policy enforcement works seamlessly here: the LLM sees the full schema, but the policy engine validates every invocation.

If you want to hide tools from certain users, you can filter the schemas before passing them to the LLM:

from langchain.tools import ToolException

def filtered_tools_for_user(user_role, all_tools):
    allowed_tool_names = get_allowed_tools(user_role)
    return [t for t in all_tools if t.name in allowed_tool_names]

tools = filtered_tools_for_user(user.role, [fetch_user_data, send_email, delete_user])
agent = create_react_agent(llm, tools)

This approach combines static filtering (which tools are visible) with runtime enforcement (can the user invoke this specific call right now). Together, they provide defense in depth.

How Vaikora Helps

Vaikora's runtime control platform automates policy enforcement for LangChain agents. The MIT-licensed gateway (vaikora-llm-gateway) is self-hostable and works with any LangChain agent without code changes: point your base_url at the gateway, and every LLM call and tool invocation is inspected.

For production deployments, the Vaikora Control Plane adds a hosted dashboard and an append-only audit chain for regulatory proof. Policies are YAML files, not code, so your security and engineering teams can collaborate on rules without waiting for releases.

Frequently asked questions

How do you secure a LangChain agent?

Secure LangChain agents with runtime policy enforcement: point the agent's LLM calls through a policy gateway that validates every action before execution. Add role-based access controls so only authorized users can invoke sensitive tools, redact PII before the agent sees it, and log all decisions for audit compliance.

What security risks do LangChain agents have?

LangChain agents face three main risks: prompt injection (adversaries embed malicious instructions in retrieved data), unauthorized tool invocation (the LLM calls a function it shouldn't have access to), and data leakage (the agent retrieves sensitive data and exposes it in logs or responses). Runtime controls block all three.

How do you prevent prompt injection in LangChain?

Prevent prompt injection by inspecting the agent's LLM prompt and responses for injection patterns before execution. A runtime policy engine can detect keywords like "ignore previous", "system override", or unusual prompt structures and block them. Redact or sanitize user input before passing it to the agent.

Can LangChain agents leak sensitive data?

Yes. An agent might retrieve PII (email, SSN, health records) during reasoning and inadvertently include it in logs, responses, or external API calls. Prevent leakage by running the agent's tool responses through a policy engine that redacts sensitive fields before the LLM sees them, and by scanning all outbound data for PII before it leaves your system.

What is an OpenAI-compatible gateway?

An OpenAI-compatible gateway is a proxy server that accepts the same API format as OpenAI's ChatCompletion endpoint but enforces custom logic before forwarding requests to the real LLM. LangChain's base_url parameter lets you point it at any OpenAI-compatible server, so you can add policy enforcement without changing your code.

How do you implement role-based access control for LangChain tools?

Implement role-based access by wrapping tool definitions with metadata about who can invoke them, then use a policy engine to validate the user's role before allowing a tool call. For example, only "admin" and "billing_manager" roles can invoke delete_subscription. The policy engine checks the user's role against the tool's requirements and blocks unauthorized calls.

What is an audit trail and why do LangChain agents need one?

An audit trail is a chronological record of every decision the system made and why. LangChain agents need audit trails to prove compliance: regulators (GDPR, HIPAA, PCI DSS) require evidence that sensitive data was handled correctly and that unauthorized actions were blocked. Each policy decision is logged with a timestamp, user, action, and reason.

Can I self-host a LangChain security gateway?

Yes. The Vaikora gateway (vaikora-llm-gateway) is MIT-licensed open-core and can be self-hosted on any infrastructure via Docker. The commercial Vaikora Control Plane adds a hosted dashboard and an append-only audit chain, but the gateway itself is free to deploy.

See Vaikora enforce policy on your AI

Open-core AI runtime control. Self-host the MIT gateway free, or run the hosted Control Plane.

Get a demo Self-host the gateway

More from the Vaikora blog