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.
LangChain's ToolNode and AgentExecutor execute tool calls with no built-in authorization check. The LLM picks the tool. Your code runs it.
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.
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.
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.
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.
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
Install the SDK
pip install veto-sdk langchain langchain-openaiDefine policies
Create veto/policies.yaml with rules for each tool. Match on name, constrain arguments, set actions.
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?
Does it work with LangGraph workflows?
What happens when a tool call is denied?
Can I use different policies for different users?
Related integrations
Wrap one LangChain tool path and inspect the decision record.