Integration patterns

Three ways to integrate the SDK — direct check, middleware wrapper, and event-based.


The SDK supports three integration patterns. Choose based on how your agent framework handles tool execution.

Direct check

Call sl.check() before each tool execution. Best for frameworks where you control the tool dispatch loop.

import { createSecurityLayer } from "@securitylayerai/sdk";

const sl = await createSecurityLayer();

async function executeTool(tool: string, params: Record<string, unknown>) {
  const result = await sl.check(tool, params);

  if (result.decision === "DENY") {
    throw new ToolBlockedError(result.reason);
  }

  if (result.decision === "REQUIRE_APPROVAL") {
    const approved = await sl.waitForApproval(result.approvalId as string);
    if (!approved) throw new ToolBlockedError("Approval denied");
  }

  return actualToolExecution(tool, params);
}

When to use: You own the tool dispatch loop and want explicit control over security decisions.

Middleware wrapper

Wrap existing tool executors with withSecurityLayer(). The wrapper intercepts calls transparently — no changes to calling code.

import { createSecurityLayer, withSecurityLayer } from "@securitylayerai/sdk";

const sl = await createSecurityLayer();

// Wrap any tool executor
const protectedExec = withSecurityLayer(sl, originalExecFunction, "exec");
const protectedWrite = withSecurityLayer(sl, originalWriteFunction, "file.write");

// Usage — identical API, security checks happen transparently
await protectedExec("git push origin main");
await protectedWrite("/tmp/output.txt", content);

You can provide custom parameter extraction when the default doesn't fit:

const protectedFetch = withSecurityLayer(sl, fetchFn, "web_fetch", {
  extractParams: (url: unknown) => ({ url }),
});

When to use: You want to add security to an existing codebase with minimal changes. Wrap the tool executors once and the rest of the code stays untouched.

Event-based

Hook into your framework's lifecycle events. Best for frameworks that emit beforeToolUse / afterToolUse events.

import { createSecurityLayer } from "@securitylayerai/sdk";

const sl = await createSecurityLayer();

// Pre-execution: check and potentially block
agentFramework.on("beforeToolUse", async (event) => {
  const result = await sl.check(event.tool, event.params);
  if (result.decision !== "ALLOW") {
    event.preventDefault();
    event.setError(`Security Layer: ${result.reason}`);
  }
});

// Post-execution: update taint tracking
agentFramework.on("afterToolUse", (event) => {
  if (event.tool === "file.read" || event.tool === "web_fetch") {
    sl.ingestContent(event.output, {
      source: event.tool === "web_fetch" ? "web" : "file",
      path: event.params.path,
      url: event.params.url,
    });
  }
});

When to use: Your framework already has a plugin/event system. The event-based pattern keeps security logic decoupled from tool execution.

Pattern comparison

AspectDirect checkMiddlewareEvent-based
Code changesModerateMinimalMinimal
ControlFullTransparentDecoupled
Taint trackingManualAutomaticManual
Best forCustom frameworksExisting codebasesPlugin architectures
Approval flowExplicitBuilt-inManual

Combining patterns

You can mix patterns in the same application:

const sl = await createSecurityLayer();

// Middleware for standard tools
const protectedExec = withSecurityLayer(sl, exec, "exec");
const protectedWrite = withSecurityLayer(sl, writeFile, "file.write");

// Direct check for custom logic
async function handleDangerousOperation() {
  const result = await sl.check("exec", {
    command: "sudo systemctl restart nginx",
  });
  // Custom handling...
}

// Event-based for taint tracking
framework.on("afterToolUse", (event) => {
  sl.ingestContent(event.output, { source: "file", path: event.params.path });
});

// Always clean up
process.on("exit", () => sl.destroy());

See also

On this page