LangChain / LangGraph integration

Prismor Warden adapter for LangChain and LangGraph. Ships from adapters/langchain/ as prismor-warden-langchain. Registry entry: id: langchain in warden/integrations/registry.yaml.

Install

pip install "prismor[langchain]"

Needs prismor >= 1.14.2. Until that version is on PyPI, the same one-liner works from source: pip install "prismor[langchain] @ git+https://github.com/PrismorSec/prismor.git@main".

Guard tools (easy path)

from langchain_core.tools import tool
from prismor.warden.langchain import guard_tools

@tool
def run_shell(command: str) -> str:
    """Execute a shell command."""
    ...

@tool
def read_file(path: str) -> str:
    """Read a file."""
    ...

tools = guard_tools([run_shell, read_file])   # every tool now policy-checked
agent = create_react_agent(llm, tools)

guard_tools wraps tool.func (sync) and tool.coroutine (async) in-place and returns the same list. Your tool schemas are untouched — the LLM sees the same tool definitions.

Guard a single tool

from prismor.warden.langchain import warden_guard_tool

guarded = warden_guard_tool(run_shell, mode="enforce", subject="user:alice")

Callback handler (alternative)

If you already use LangChain callbacks or need to guard an agent executor without modifying its tool list, use WardenCallbackHandler:

from prismor.warden.langchain import WardenCallbackHandler

handler = WardenCallbackHandler(mode="enforce")
agent_executor.invoke({"input": prompt}, config={"callbacks": [handler]})

The handler fires on on_tool_start — before the tool executes. It raises WardenBlocked in enforce mode, which aborts the tool call and surfaces as an error in the agent's output.

Note: guard_tools (direct wrapping) blocks before the tool body runs and the model recovers gracefully. WardenCallbackHandler raises an exception that interrupts execution — use it when you can't modify the tool list.

Per-user control (multi-tenant)

from prismor.warden.langchain import guard_tools, use_subject

tools = guard_tools([run_shell, read_file])   # once, at startup — no bound subject
agent = create_react_agent(llm, tools)

# per-request handler
with use_subject("user:alice"):
    agent.invoke({"messages": [("user", prompt)]})

The use_subject context manager sets the subject for every tool call that runs inside the block. Thread-safe and async-safe (contextvar-backed) — concurrent requests each see their own subject.

Per-user IAM example (.prismor/iam.yaml):

agents:
  user:bob:
    deny_tools: [Bash]
    deny_network: true
    allowed_paths: ["**"]

user:bob will have run_shell blocked by IAM even if the call would otherwise be allowed by the org-wide policy. user:alice (no profile) gets org defaults.

LangGraph

Works identically — guard the tools before passing them to the graph:

from langgraph.prebuilt import create_react_agent
from prismor.warden.langchain import guard_tools, use_subject

tools = guard_tools([run_shell, search])
graph = create_react_agent(llm, tools)

with use_subject("user:alice"):
    result = graph.invoke({"messages": [("user", prompt)]})

Event mapping

Tool call typeEvent typeFieldRules that apply
Default (shell commands, etc.)shellcommanddestructive-command, secret-exfiltration, …
Override with event_type="network"networkurlsuspicious-network, secret-in-url-params
Override with event_type="file_write"file_writepathpath-based rules

Pass event_type and command_builder to warden_guard_tool for tools whose risk is a URL or path rather than a shell command.

Reference

SymbolPurpose
guard_tools(tools, **kwargs)Guard a list of LangChain tools in one call
warden_guard_tool(tool, **kwargs)Guard a single tool
WardenCallbackHandler(**kwargs)Callback-based guard (for agent executors)
use_subject(value)Per-request subject contextmanager
WardenBlockedRaised on enforce-mode block (when raise_on_block=True)

All functions accept: subject, workspace, agent, mode, session_id, event_type, raise_on_block. See adapters/langchain/ for full signatures.