Securing LangChain applications
Where a security layer attaches in a LangChain agent — the documented middleware hooks, what each one can enforce, and what middleware alone cannot cover.
LangChain gives you two places to put a security control: inside the agent loop as middleware, or outside the process entirely by pointing the model client at something you run. They enforce different things and the honest answer is that serious deployments use both. This page describes each from LangChain's own documentation, and states plainly what we do and do not claim about our support for it.
We publish no tested-compatibility claim for LangChain. Nothing on this page should be read as "SecureAI Guard supports LangChain version X". What follows is the attachment points LangChain documents and what a control placed at each of them can actually enforce — useful whether you buy anything or write it yourself. What we have and have not tested belongs in the supported models and frameworks matrix, which is not filled in yet.
The documented attachment point: agent middleware
LangChain's Python documentation describes middleware as "a way to more tightly control what happens inside the agent", covering exactly the use cases this page is about — transforming prompts and outputs, adding retries and fallbacks, and applying rate limits and guardrails. Middleware runs around the steps of the agent loop, and the documented custom hooks are:
| Hook | Runs | Shape |
|---|---|---|
before_agent | once per invocation, before the agent starts | node-style |
before_model | before each model call | node-style |
after_model | after each model response | node-style |
after_agent | once per invocation, after the agent completes | node-style |
wrap_model_call | around each model call | wrap-style |
wrap_tool_call | around each tool call | wrap-style |
dynamic_prompt | generates the system prompt | convenience |
Node-style hooks take the agent state and the runtime and return an optional state update:
@before_model
def my_hook(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
return NoneWrap-style hooks receive the request and the downstream handler, which is what makes them the interesting ones for enforcement — the hook decides whether to call the handler at all, and it sees the response before anything else does:
@wrap_model_call
def my_wrapper(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
return handler(request)Source: LangChain custom middleware documentation, read 13 August 2026. Check it before you build — the hook set has changed as the agent abstraction has evolved, and this table is a snapshot.
What a control at each hook can actually enforce
before_modelsees the request the agent is about to send, including the assembled context: retrieved documents, replayed history, tool definitions. This is the natural place for input-side inspection — prompt-injection detection over the full context rather than over "what the user typed", which is usually a small fraction of it.wrap_model_callis the only hook that can both inspect the request and refuse to make it, and then inspect the completion before the agent acts on it. Output-side enforcement — redaction, blocking a disclosure, holding a response for review — has to live here or further out. Anafter_modelhook observes the response; a wrapper controls it.wrap_tool_callis where the agent stops being a chat application. It sees the tool the model chose and the arguments it chose, before execution. That is the last point at which "the model asked to delete the account" is still a proposal rather than an event.before_agent/after_agentbracket the whole invocation, which makes them the right place for per-invocation budgets and for the audit record, not for content decisions.
What middleware alone cannot do
This is the part most integration pages leave out, and it is the reason we said "both" at the top.
Middleware runs in your process. It is a library call in the same runtime as the agent, so it enforces policy for code that goes through the agent — and only for that code. Any path that constructs a model client directly, a background job, a notebook, a second service, a hastily-added endpoint, does not pass through your middleware and is not covered by it. As the application grows, the proportion of model traffic that reaches the model without touching the agent loop tends to grow with it.
It is only as trustworthy as the process. A control inside the process it is protecting cannot make guarantees about that process. This is a weaker version of the reason a browser-side filter is not a control, and it matters most when the thing you are defending against is an agent that can execute code.
It cannot see what the framework does not carry. Retrieval happens before the model call. If your retriever returns a document the user should not have been able to read, before_model sees a context window that already contains it, and blocking the model call does not un-retrieve it. Retrieval-time authorisation belongs at the retriever — see RAG security for why that is the enforcement point.
The complement is an out-of-process layer that every model call reaches regardless of the code path, which for most stacks means pointing the model client's base URL at it. That pattern is described on the Anthropic API and Vertex AI pages and works the same way here.
A reasonable deployment shape
- Out-of-process enforcement for the non-negotiable controls. Everything that must apply to every model call, no exceptions: output-side disclosure controls, PII handling, hard policy blocks. Placed where the code path cannot route around it.
- Middleware for the context-rich checks.
wrap_model_callandwrap_tool_callknow which agent, which step, which tool and which user — context a network-level layer does not have. Use it for decisions that need that context, and for tool-call gating. - Authorisation at the tool and at the retriever, always. Neither of the above can substitute for it. AI agent security sets out why.
Run stages one and two in log-only mode first. The measurement procedure is in detection accuracy and false positives, and the reason it matters more than the detection rate is arithmetic rather than opinion.
What to ask us before you build against this
Because we do not publish a compatibility claim, the useful questions are specific:
- Which attachment point do you support for LangChain today — middleware, a base-URL proxy, or an explicit check call?
- If middleware: which hooks, which package and version range, and is it maintained by you?
- Does an enforcement decision reach
wrap_tool_call, or only the model call? - What happens to the agent run when the layer cannot reach a decision?
Get the answers in writing. Evaluation and security review has the rest of the list.