One-Line AI Agent Security Middleware
Agent Trust, MCP, & Delegation Chains
Section titled “Agent Trust, MCP, & Delegation Chains”The One-Line AI Agent Security Middleware Pattern
Section titled “The One-Line AI Agent Security Middleware Pattern”In modern autonomous architectures (built on Vercel AI SDK, LangChain, CrewAI, or raw Model Context Protocol (MCP) clients), AI agents dynamically discover and invoke tools. Without pre-execution cryptographic enforcement, systems are vulnerable to:
- Tool Schema Drift & Rug-Pulls: An upstream MCP server silently changes a tool definition or injects an unvetted parameter after onboarding.
- Unauthorized Capability Invocations: An agent attempts an action (e.g.
payments:refundordatabase:drop) that exceeds its delegated authority. - Expired or Tampered Delegation Chains: A delegation receipt has expired or had its capability scope illegally expanded.
DID.is provides a high-level fail-closed middleware pattern that wraps any agent tool in a single line of code, verifying live tool drift and cryptographic authorization (didis.verifyTool) prior to execution.
Any missing parameter, unresolvable issuer DID, invalid cryptographic signature, or detected tool definition drift immediately aborts tool execution with a typed DidisAuthorizationError. No unverified payload reaches agent business logic.
Universal Middleware Implementations
Section titled “Universal Middleware Implementations”Inspect the complete TypeScript and Python guard implementations below:
1
import { DidisClient, type ToolAuthorization } from "@didis/client";
2
3
const defaultClient = new DidisClient();
4
5
export interface DidisGuardOptions {
6
/** The DID representing the executing agent (e.g. leaf did:key or did:web). */
7
agent: string;
8
/** The capability/tool string to authorize (e.g. "payments:refund"). */
9
tool: string;
10
/** Optional trusted root authority DID that the delegation must stem from. */
11
root?: string;
12
/** Optional Streamable HTTP MCP server URL. When set, asserts zero tool drift before running. */
13
mcpEndpoint?: string;
14
/** Optional custom DidisClient instance (e.g. targeting local daemon). */
15
client?: DidisClient;
16
}
17
18
/**
19
* Wraps any asynchronous tool handler with pre-execution DID.is authorization and drift checks.
20
* Throws immediately if authorization fails or if tool definition drift is detected.
21
*/
22
export function withDidisGuard<TArgs, TResult>(
23
toolHandler: (args: TArgs) => Promise<TResult>,
24
options: DidisGuardOptions
25
): (args: TArgs) => Promise<TResult> {
26
const client = options.client ?? defaultClient;
27
28
return async (args: TArgs): Promise<TResult> => {
29
// 1. Optional MCP Drift & Rug-Pull Protection
30
if (options.mcpEndpoint) {
31
const inspection = await client.inspectMcp(options.mcpEndpoint);
32
if (inspection.drift.status === "CHANGED" || inspection.drift.status === "REMOVED") {
33
throw new Error(
34
`[DID.is Security Block] Tool server drift detected on ${options.mcpEndpoint}: ` +
35
`${inspection.drift.status}. Execution halted to prevent prompt injection.`
36
);
37
}
38
}
39
40
// 2. Cryptographic Delegation Verification
41
const auth: ToolAuthorization = await client.verifyTool(
42
options.agent,
43
options.tool,
44
options.root
45
);
46
47
if (!auth.authorized || auth.decision !== "ALLOW") {
48
throw new Error(
49
`[DID.is Authorization Denied] Agent '${options.agent}' is not authorized to invoke tool ` +
50
`'${options.tool}': ${auth.reason}`
51
);
52
}
53
54
// 3. Authorized — Proceed to Tool Execution
55
return toolHandler(args);
56
};
57
}
1
import functools
2
from typing import Any, Callable, Optional
3
from didis import DidisClient, DidisError
4
5
default_client = DidisClient()
6
7
class DidisAuthorizationError(PermissionError):
8
"""Raised when an agent tool invocation fails cryptographic delegation checks."""
9
10
def didis_guard(
11
agent: str,
12
tool: str,
13
root: Optional[str] = None,
14
mcp_endpoint: Optional[str] = None,
15
client: Optional[DidisClient] = None
16
) -> Callable:
17
"""Universal pre-execution security decorator for CrewAI, LangChain, and MCP Python tools."""
18
didis = client or default_client
19
20
def decorator(fn: Callable) -> Callable:
21
@functools.wraps(fn)
22
def wrapper(*args: Any, **kwargs: Any) -> Any:
23
# 1. Optional MCP Tool Drift Protection
24
if mcp_endpoint:
25
inspection = didis.inspect_mcp(mcp_endpoint)
26
drift_status = inspection.get("drift", {}).get("status")
27
if drift_status in ("CHANGED", "REMOVED"):
28
raise DidisAuthorizationError(
29
f"MCP tool catalog drift detected on {mcp_endpoint}: {drift_status}. Refusing execution."
30
)
31
32
# 2. Cryptographic Delegation Verification
33
auth = didis.verify_tool(agent=agent, tool=tool, root=root)
34
if not auth.get("authorized") or auth.get("decision") != "ALLOW":
35
raise DidisAuthorizationError(
36
f"Agent '{agent}' is unauthorized to invoke tool '{tool}': {auth.get('reason')}"
37
)
38
39
# 3. Authorized — Proceed to Tool Execution
40
return fn(*args, **kwargs)
41
return wrapper
42
return decorator
Framework Integration Examples
Section titled “Framework Integration Examples”Example 1: Vercel AI SDK Integration
Section titled “Example 1: Vercel AI SDK Integration”import { tool } from "ai";import { z } from "zod";import { withDidisGuard } from "./didisGuard";
export const agentTools = { // One-line wrapped secure payment tool refundPayment: withDidisGuard( tool({ description: "Process a refund for an e-commerce customer transaction", parameters: z.object({ paymentId: z.string(), amountCents: z.number().int() }), execute: async ({ paymentId, amountCents }) => { // Business logic runs ONLY if cryptographic authorization passes return { success: true, paymentId, amountCents }; }, }), { agent: "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", tool: "payments:refund", root: "did:web:enterprise.example" } )};Example 2: CrewAI Python Integration
Section titled “Example 2: CrewAI Python Integration”from crewai.tools import toolfrom security_guard import didis_guard
# One-line decorator guarding agent execution@tool("Refund Transaction")@didis_guard( agent="did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", tool="payments:refund", root="did:web:enterprise.example")def process_refund(payment_id: str, amount: float) -> str: """Executes a financial refund against the ledger.""" return f"Refunded {amount} for {payment_id}"Example 3: LangChain.js Integration
Section titled “Example 3: LangChain.js Integration”import { DynamicStructuredTool } from "@langchain/core/tools";import { z } from "zod";import { withDidisGuard } from "./didisGuard";
const rawRefundTool = new DynamicStructuredTool({ name: "refund_payment", description: "Issues a refund to a specified order ID", schema: z.object({ orderId: z.string() }), func: async ({ orderId }) => `Order ${orderId} refunded successfully.`});
// Single-line execution guard interceptorrawRefundTool.invoke = withDidisGuard( rawRefundTool.invoke.bind(rawRefundTool), { agent: "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", tool: "payments:refund", root: "did:web:enterprise.example" });Streamable HTTP MCP Server Inspection
Section titled “Streamable HTTP MCP Server Inspection”Audit Model Context Protocol (MCP) tool servers:
curl -s "https://did.is/api/v1/mcp/inspect?endpoint=https://mcp.example.com/mcp" | jq '{ status: .status, mode: .mode, toolsCount: (.tools | length), inventoryHash: .inventoryHash, drift: .drift.status}'import { DidisClient } from "@didis/client";
const didis = new DidisClient();const inspection = await didis.inspectMcp("https://mcp.example.com/mcp");
console.log(`Server Protocol: ${inspection.mode} (${inspection.negotiatedVersion})`);console.log(`Inventory Hash: ${inspection.inventoryHash}`);console.log(`Catalog Drift: ${inspection.drift.status}`);
for (const tool of inspection.tools) { console.log(`Tool: ${tool.name} (Declared: ${tool.declaredClass}, Heuristic: ${tool.heuristicClass})`); if (tool.riskSignals.length > 0) { console.warn(` ⚠️ Risk signals: ${tool.riskSignals.join(", ")}`); }}Tool Schema Fingerprints & Drift Auditing
Section titled “Tool Schema Fingerprints & Drift Auditing”definitionSha256: SHA-256 of RFC 8785 canonical tool JSON.schemaSha256: SHA-256 of tool parameter schema.inventoryHash: Deterministic composite hash over all sorted tool fingerprints.- Drift States:
FIRST_OBSERVATION,UNCHANGED,ADDED,REMOVED,CHANGED,PROFILE_CHANGED.
A2A Agent Card Signature Verification
Section titled “A2A Agent Card Signature Verification”Audit Agent-to-Agent protocol discovery cards:
const card = await didis.inspectA2a("https://agent.example");
console.log(`Card URL: ${card.cardUrl}`);console.log(`Provider: ${card.provider?.organization}`);console.log("Signatures:");for (const sig of card.signatures) { console.log(` [${sig.status}] ${sig.alg} via ${sig.kid ?? sig.jku}`);}DID.is Delegation Receipt v1 Verification
Section titled “DID.is Delegation Receipt v1 Verification”Verify multi-hop capability delegation chains without centralized servers:
// Chain array ordered root firstconst chain = [ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZGlzLWRlbGVnYXRpb24rand0Ii...", // Root: human -> org "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZGlzLWRlbGVnYXRpb24rand0Ii...", // Hop 1: org -> orchestrator "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZGlzLWRlbGVnYXRpb24rand0Ii..." // Hop 2: orchestrator -> worker];
const { result } = await didis.verifyDelegation(chain, { trustedRoots: ["did:web:enterprise.example"], register: true // Persists valid chain in core SQLite for fast lookup});
console.log(`Chain Status: ${result.status}`);console.log(`Effective Capabilities: ${result.effectiveCapabilities.join(", ")}`);Tool Authorization Engine (verify-tool)
Section titled “Tool Authorization Engine (verify-tool)”Verify if an agent is authorized to invoke a tool:
// Stateless check against a supplied chainconst auth = await didis.authorize(chain, "payments:refund");console.log(`Decision: ${auth.authorization.decision}`); // ALLOW or DENY
// Fast check against registered, unexpired chains stored in core SQLiteconst registeredAuth = await didis.verifyTool( "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", "payments:refund", "did:web:enterprise.example");console.log(`Registered Decision: ${registeredAuth.decision}`); // ALLOW