Skip to content

What stops your LangChain agent from deleting your database?

Published April 2026 ·


Here's a question most teams building with LangChain haven't thought through yet: when your agent decides to call a tool, what stops it from calling the wrong one?

Most answers fall into one of three categories:

  • "The prompt tells it what to do"
  • "We trust the LLM"
  • "We haven't really thought about it"

All three are reasonable until something goes wrong. And in production, something always goes wrong.

The problem in one scenario

Imagine you have an invoice processing agent. It reads pending invoices, approves the ones under $50k, and sends confirmation emails. It's been working fine for weeks.

Then one day, through a combination of an unusual input, a slightly different prompt, and an LLM that decided to be helpful in an unexpected way, it calls delete_invoice on a $87,000 record.

Here's what that looks like without any enforcement layer:

  • [Agent] Calling delete_invoive on INV-003...
  • Result: Invoice INV-003 deleted.
  • Records deleted: 1
  • Deleted IDs: ['INV-003']

No error. No alert. No audit entry. The record is gone. You find out in the next reconciliation run.

Now here's the same scenario with Scopebound: - [Agent] Calling: delete_invoice on INV-003... - invoice_id=INV-003 - ✗ DENIED in 1.2ms - deny_code: SCOPE_VIOLATION - severity: medium - reason: tool not in allowed_tools - session_id: sess-abc123

The tool did not execute. INV-003 still exists. Denial recorded in audit log with SHA-256 hash chain entry #47. The _run() body never executed. One line in the audit log. The $87k record is intact.

How it works

Scopebound sits inside the LangChain execution loop — not at the network boundary, not as a proxy, but between the LLM's decision and the tool's execution.

The integration is a single decorator:

from scopebound import ScopeboundSDK, enforce
from langchain_core.tools import BaseTool

sb = ScopeboundSDK()

@enforce(sb, role="invoice-processor")
class ReadInvoicesTool(BaseTool):
    name: str = "read_invoices"
    description: str = "Read pending invoices"

    def _run(self, status: str = "pending") -> str:
        return get_invoices(status)

That's the entire integration. Your existing tool code is unchanged.

When _run() is called, Scopebound intercepts it first. It checks the agent's scoped JWT against a policy that says: the invoice-processor role is allowed to call read_invoices and send_email — nothing else. If the tool name is in the allowed list, the call proceeds. If not, ScopeboundDenyError is raised before the method body executes.

The policy evaluation takes under 5ms. The audit log entry is written atomically with the response.

What the audit log looks like

Every decision — allow and deny — is recorded:

{
  "event_type": "TOOL_CALL_DENIED",
  "timestamp": "2026-04-15T14:23:11.442Z",
  "session_id": "sess-abc123",
  "agent_id": "invoice-processor-v2",
  "tool_name": "delete_invoice",
  "decision": "deny",
  "deny_code": "SCOPE_VIOLATION",
  "severity": "medium",
  "reason": "tool not in allowed_tools",
  "hash": "sha256:a3f8b2...",
  "prev_hash": "sha256:9e1c4f..."
}

The hash chain means you can verify that no entries were tampered with or deleted after the fact. If someone deletes a log entry, the chain breaks and the server halts.

Why "just add a guard in the tool" doesn't scale

The obvious alternative is to add enforcement logic inside each tool's _run() method. Teams do this all the time. The problem is that it doesn't compose:

  • Every new tool needs its own guard
  • Guards are inconsistent across tools and teams
  • When you upgrade LangChain or migrate to CrewAI, you rewrite all of it
  • There's no central audit trail — you'd need to add logging to every tool too
  • None of it survives a framework migration

Scopebound's enforcement is framework-native but framework-agnostic. The same @enforce decorator works across LangChain, AutoGen, CrewAI, Semantic Kernel, OpenAI Assistants, Claude Agent SDK, and MCP. The policy, the audit log, and the identity layer are shared across all of them.

The drift detection problem

Rate limiting and policy enforcement handle known-bad behaviour. But what about an agent that starts behaving anomalously — not calling a forbidden tool, but calling an allowed one at 10× its normal frequency?

This is the pattern of a compromised agent or a prompt injection attack. The agent is doing something it's technically allowed to do, just at a scale that signals something is wrong.

Scopebound's behavioral drift detector builds a statistical baseline per session and uses an Isolation Forest to score each call. When the anomaly score crosses a threshold, the session is automatically revoked. The agent stops mid-task. Every call after revocation returns BEHAVIORAL_DRIFT.

What we built

  • Framework-native enforcement via decorator pattern — no proxy, no gateway, no network hop
  • OPA/Rego policy engine — policies are code, version-controlled alongside your agents
  • Scoped JWTs per agent session — role, allowed tools, TTL, and rate limits baked into the token
  • Tamper-evident audit log with SHA-256 hash chain
  • Behavioral drift detection with automatic session revocation
  • Webhook alerts on policy violations — POST to your endpoint within 2 seconds
  • Rate limiting per agent role — max calls per minute and per hour
  • MCP gateway enforcement — same pipeline for MCP tool calls via POST /v1/mcp/enforce
  • Usage analytics endpoint — call volume, allow/deny rates, top denied tools

Six framework adapters. One enforcement plane. Under 5ms on the enforce path.

Try it

pip install scopebound

Full docs at docs.scopebound.ai. Demo repo at github.com/scopebound-ai/scopebound-demo.

If you're building agents in production and want to talk about what we're working on, email us at [email protected].


Scopebound is a zero-trust enforcement layer for AI agents. We're working with a small number of design partners — teams building agents in production who want enforcement, auditability, and drift detection without building it themselves.