LangChain runtime authorization

Wrap LangChain tools and chains with Veto. Each governed tool call is evaluated before dispatch: allow, review, or deny, with an exportable decision record per governed decision.

The problem with LangChain tool execution

LangChain makes it straightforward to give agents tools. Decorate a function with @tool, pass it to an agent, and the LLM can call it. But LangChain provides no enforcement layer between the model's decision and the tool's execution. The framework "cheerfully parses the JSON and executes whatever parameters the LLM outputs."

The framework is not the guardrail boundary. Even when framework bugs are patched, your tools remain the application-level attack path: database writes, shell commands, customer messages, payments, and internal APIs. Those calls need a policy decision before execution, not only a model decision.

No pre-execution auth

LangChain's ToolNode and AgentExecutor execute tool calls with no built-in authorization check. The LLM picks the tool. Your code runs it.

Application tool surface

The review point is the same pattern across agent frameworks: the LLM picks a tool and emits arguments. Veto checks the governed call before your function runs.

No decision record

LangChain does not log tool calls with decision context. For SOC 2, HIPAA, or financial compliance you need to know what was attempted and what was blocked.

Before and after Veto

The left tab shows a standard LangChain agent. The LLM picks tools and LangChain executes them unconditionally. The right tab adds Veto inside each tool function. Same agent, same tools, each governed call evaluated against policy first.

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

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to a recipient."""
    return email_service.send(to, subject, body)

@tool
def delete_records(table: str, condition: str) -> str:
    """Delete records from a database table."""
    return db.execute(f"DELETE FROM {table} WHERE {condition}")

@tool
def process_payment(amount: float, recipient: str) -> str:
    """Process a payment transaction."""
    return payment_service.charge(amount, recipient)

llm = ChatOpenAI(model=os.environ["OPENAI_MODEL"])
tools = [send_email, delete_records, process_payment]

agent = create_react_agent(llm, tools)
executor = AgentExecutor(agent=agent, tools=tools)

# The LLM decides which tools to call and with what arguments.
# LangChain executes them. No enforcement. No limits.
# A prompt injection could trigger delete_records on your users table.
result = executor.invoke({"input": user_message})

LangGraph ToolNode integration

LangGraph's create_react_agent builds a state machine with a ToolNode that executes tool calls. Veto validates inside each tool function, so the graph topology and state management stay outside the policy surface.

langgraph_with_veto.py
import os
from langgraph.graph import StateGraph, MessagesState
from langgraph.prebuilt import ToolNode, create_react_agent
from langchain_core.tools import tool
from veto_sdk import Veto

veto = Veto(api_key=os.environ["VETO_API_KEY"])

@tool
def query_database(query: str, table: str) -> str:
    """Execute a database query."""
    decision = veto.guard(
        tool="query_database",
        arguments={"query": query, "table": table},
    )
    if decision.decision != "allow":
        return f"Blocked: {decision.reason}"
    return db.execute(query, table)

@tool
def write_file(path: str, content: str) -> str:
    """Write content to a file."""
    decision = veto.guard(
        tool="write_file",
        arguments={"path": path, "content": content},
        context={"environment": "production"},
    )
    if decision.decision != "allow":
        return f"Blocked: {decision.reason}"
    return fs.write(path, content)

tools = [query_database, write_file]

# create_react_agent builds the full LangGraph with ToolNode
# Veto validates inside each tool before side effects occur
agent = create_react_agent(
    model=f"openai:{os.environ['OPENAI_MODEL']}",
    tools=tools,
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "Query the users table"}]},
    config={"configurable": {"thread_id": "session-123"}},
)

Policy configuration

Define what your LangChain agents can and cannot do. Declarative YAML, version controlled, no prompt engineering.

veto/policies.yaml
rules:
  - name: block_destructive_queries
    description: Prevent DELETE, DROP, TRUNCATE on any table
    tool: delete_records
    when: context.environment == "production"
    action: deny
    message: "Destructive operations blocked in production"

  - name: read_only_database
    description: Only allow SELECT queries
    tool: query_database
    when: "!args.query.upper().startswith('SELECT')"
    action: deny
    message: "Only read-only queries are permitted"

  - name: block_sensitive_tables
    description: Block access to credentials and PII tables
    tool: query_database
    when: args.table in ["credentials", "passwords", "ssn"]
    action: deny
    message: "Access to sensitive tables is prohibited"

  - name: approve_external_email
    description: Require approval for non-company emails
    tool: send_email
    when: "!args.to.endswith('@approved.example')"
    action: require_approval
    approvers: [compliance-team]

  - name: payment_limits
    description: Cap payments at $5,000
    tool: process_payment
    when: args.amount > 5000
    action: deny
    message: "Payments over $5,000 require manual processing"

  - name: block_system_paths
    description: Prevent writes to system directories
    tool: write_file
    when: args.path.startswith("/etc") || args.path.startswith("/sys")
    action: deny
    message: "Cannot write to system directories"

First governed call

1

Install the SDK

pip install veto-sdk langchain langchain-openai
2

Define policies

Create veto/policies.yaml with rules for each tool. Match on name, constrain arguments, set actions.

3

Add veto.guard() to each tool

Call veto.guard() at the top of each tool function. Check the decision. Execute only if allowed. The policy check sits at the tool boundary, so policy stays inside tool functions rather than agent, executor, or graph wiring.

What Veto covers for LangChain agents

Works at the tool boundary

ReAct agents, Plan-and-Execute, create_react_agent, custom AgentExecutor subclasses. Veto validates inside the tool function, so agent type is irrelevant.

Argument constraints

Block SQL containing DELETE/DROP. Restrict file paths to approved directories. Cap payment amounts. Enforce at the data level, not the prompt level.

Human review

Route sensitive tool calls to human approval queues. Works alongside LangGraph's interrupt() for graph-level pause/resume. Different mechanisms, complementary goals.

Decision record

Governed tool calls can log name, arguments, decision, reason, and timestamp. Queryable via API. Export for evidence review. LangSmith traces show what happened; Veto records show what was allowed.

Frequently asked questions

Does Veto replace LangChain's middleware system?
No. Veto validates inside tool functions, which works inside LangChain tool functions. If you also use LangChain's @wrap_tool_call middleware, Veto can complement it. Middleware intercepts at the executor level; Veto intercepts at the function level. Use both for defense in depth.
Does it work with LangGraph workflows?
Yes. Veto validates inside tool functions called by LangGraph's ToolNode. The graph topology, state management, and checkpointing are unaffected. See the LangGraph integration page for graph-level guardrail patterns.
What happens when a tool call is denied?
The tool function returns a string explaining the denial (e.g., 'Blocked: Destructive operations blocked in production'). The agent sees this as a normal tool response and can adjust its approach. You can also raise an exception or return a custom fallback.
Can I use different policies for different users?
Yes. Pass user context (role, org, environment) in the veto.guard() call. Policies can condition on any context field. An admin might be allowed to delete records while a viewer is restricted to read-only queries.

Related integrations

Wrap one LangChain tool path and inspect the decision record.