How to Add Safety Gating to LangChain Agents
SafeClaw by Authensor intercepts every tool invocation in LangChain's agent execution loop, enforcing deny-by-default policies before any tool's _run() method executes. LangChain agents decide which tools to call and with what arguments — SafeClaw sits between that decision and the actual execution, evaluating each call against your YAML policy in sub-millisecond time.
How LangChain Tool Calling Works
LangChain agents use a Tool abstraction where each tool has a name, description, and a _run() method. The agent (whether using AgentExecutor, create_react_agent, or the newer langgraph patterns) sends the available tools to the LLM, receives a tool call decision, and invokes the tool. The execution flow is: LLM output -> parse tool call -> invoke tool -> return observation. SafeClaw intercepts at the "invoke tool" step.
LLM Decision → Tool Name + Args → [SafeClaw Policy Check] → tool._run() or Deny
Quick Start
npx @authensor/safeclaw
Creates a safeclaw.yaml in your project root. SafeClaw maps LangChain tool names directly to policy rules.
Step 1: Define Policies for LangChain Tools
# safeclaw.yaml
version: 1
default: deny
policies:
- name: "langchain-search-tools"
description: "Allow search and retrieval tools"
actions:
- tool: "tavily_search"
effect: allow
- tool: "retriever"
effect: allow
- tool: "wikipedia"
effect: allow
- name: "langchain-code-tools"
description: "Control code execution tools"
actions:
- tool: "python_repl"
effect: allow
constraints:
blocked_imports: ["os", "subprocess", "shutil", "socket"]
- tool: "shell"
effect: deny
- name: "langchain-data-tools"
description: "Control database access"
actions:
- tool: "sql_database"
effect: allow
constraints:
operation: "SELECT"
- tool: "sql_database"
effect: deny
constraints:
operation: "DROP|DELETE|TRUNCATE|ALTER"
- name: "langchain-file-tools"
description: "Restrict file operations"
actions:
- tool: "read_file"
effect: allow
constraints:
path_pattern: "data/|docs/"
- tool: "write_file"
effect: allow
constraints:
path_pattern: "output/**"
- tool: "file_delete"
effect: deny
Step 2: Wrap LangChain's Tool Execution
SafeClaw integrates as a wrapper around LangChain's tool invocation:
from langchain.tools import Tool
from langchain.agents import AgentExecutor, create_react_agent
from safeclaw import SafeClaw
safeclaw = SafeClaw("./safeclaw.yaml")
Wrap each tool with SafeClaw gating
def gate_tool(tool: Tool) -> Tool:
original_run = tool._run
def safe_run(args, *kwargs):
decision = safeclaw.evaluate(tool.name, {"args": args, "kwargs": kwargs})
if not decision.allowed:
return f"Action denied by SafeClaw: {decision.reason}"
return original_run(args, *kwargs)
tool._run = safe_run
return tool
Apply to all tools
tools = [gate_tool(t) for t in [search_tool, sql_tool, file_tool]]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
Step 3: Integrate with LangGraph Agents
For LangGraph-based agents (the newer pattern), SafeClaw integrates at the tool node:
from langgraph.prebuilt import ToolNode
from safeclaw import SafeClaw
safeclaw = SafeClaw("./safeclaw.yaml")
def safe_tool_node(state):
"""Custom tool node with SafeClaw gating."""
messages = state["messages"]
last_message = messages[-1]
results = []
for tool_call in last_message.tool_calls:
decision = safeclaw.evaluate(tool_call["name"], tool_call["args"])
if decision.allowed:
tool = tool_map[tool_call["name"]]
result = tool.invoke(tool_call["args"])
results.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
else:
results.append(ToolMessage(
content=f"Denied: {decision.reason}",
tool_call_id=tool_call["id"]
))
return {"messages": results}
Use safe_tool_node instead of ToolNode in your graph
graph.add_node("tools", safe_tool_node)
Step 4: Handle Agent Loops
LangChain agents iterate until they reach a final answer. SafeClaw evaluates each tool call independently across iterations, and the audit log captures the full reasoning chain:
# The agent loop calls tools multiple times
SafeClaw evaluates each independently
result = executor.invoke({"input": "Analyze Q4 sales data and write a report"})
Review what happened
npx @authensor/safeclaw audit --last 20
Step 5: Policy Per Chain
Different LangChain chains can use different SafeClaw policies:
# Research chain — broad read access
research_safeclaw = SafeClaw("./policies/research.yaml")
Action chain — restricted write access
action_safeclaw = SafeClaw("./policies/action.yaml")
Why SafeClaw
- 446 tests covering policy evaluation, edge cases, and audit integrity
- Deny-by-default — unlisted tools are blocked automatically
- Sub-millisecond evaluation — no delay in LangChain's agent loop
- Hash-chained audit log — full trace of every tool invocation across iterations
- Works with Claude AND OpenAI — supports any LLM backend LangChain connects to
Related Pages
- How to Secure CrewAI Multi-Agent Systems
- How to Add Action Gating to LlamaIndex Agents
- How to Add Safety Controls to AutoGen Agents
- How to Secure Your Claude Agent with SafeClaw
Try SafeClaw
Action-level gating for AI agents. Set it up in your browser in 60 seconds.
$ npx @authensor/safeclaw