← Back to Blog
TypeScriptNode.jsAgentGuardPolicy EngineAI SafetyLeast PrivilegeTokenFence

Ship Least-Privilege AI Agents in TypeScript: The TokenFence Policy Engine Hits Node.js

·8 min read

The Policy Engine Crosses the Language Barrier

Two days ago, we shipped the AgentGuard Policy engine in TokenFence's Python SDK. The response was immediate: "When is this coming to Node.js?"

Today. Right now. npm install tokenfence@0.2.0.

If you're building AI agents in TypeScript — whether with the Vercel AI SDK, LangChain.js, or raw OpenAI/Anthropic clients — you now have the same least-privilege enforcement that Python developers get. Zero dependencies. Full TypeScript types. 53 tests passing.

Why AI Agents Need Policies, Not Just Prompts

The pattern is becoming painfully clear in 2026:

  1. Developer builds an AI agent with tool access
  2. Agent gets system prompt: "You can read the database but never delete anything"
  3. Agent encounters an edge case the prompt didn't cover
  4. Data is lost, emails are sent, files are deleted

Prompts are suggestions. Policies are enforcement. The Meta AI agent incident, the Grigorev database wipe — both would have been prevented by a deny list that the agent literally cannot bypass.

How It Works in TypeScript

The API is designed to be instantly familiar to TypeScript developers:

import { Policy } from "tokenfence";

// Define what your database agent can do
const policy = new Policy({
  name: "database-agent",
  allow: ["db_read_*", "db_list_*", "db_count_*"],
  deny: ["db_delete_*", "db_drop_*", "db_truncate_*"],
  requireApproval: ["db_write_*", "db_update_*"],
  default: "deny",  // anything not listed = blocked
});

// Before every tool call, check the policy
const result = policy.check("db_read_users");
console.log(result.allowed);  // true

const dangerous = policy.check("db_drop_table");
console.log(dangerous.denied);  // true
console.log(dangerous.reason);  // "denied by pattern: db_drop_*"

Enforce Mode: Throw on Denied

For production agents, use enforce() instead of check(). It throws a ToolDenied exception when a tool call is blocked — your agent literally cannot proceed with the dangerous action:

import { Policy, ToolDenied } from "tokenfence";

const policy = new Policy({
  allow: ["read_*", "search_*"],
  deny: ["delete_*", "drop_*"],
});

try {
  policy.enforce("delete_user_data");
} catch (e) {
  if (e instanceof ToolDenied) {
    console.log(e.tool);        // "delete_user_data"
    console.log(e.reason);      // "denied by pattern: delete_*"
    console.log(e.matchedRule); // "delete_*"
  }
}

Approval Gates: Human-in-the-Loop Where It Matters

Some operations aren't dangerous enough to block outright, but you want a human to confirm. The requireApproval pattern with onApproval callbacks gives you exactly this:

const policy = new Policy({
  allow: ["email_read_*", "email_draft_*"],
  deny: ["email_delete_*"],
  requireApproval: ["email_send_*"],
  onApproval: (result) => {
    // Your approval logic — check a queue, ask a human, etc.
    return userApproved(result.tool);
  },
});

// Reads go through instantly
policy.check("email_read_inbox");  // allowed

// Sends get routed through approval
policy.check("email_send_newsletter");  // depends on callback

Full Audit Trail

Every policy decision is recorded with timestamps. This is critical for compliance, debugging, and understanding what your agents actually did:

const policy = new Policy({
  allow: ["*"],
  deny: ["rm_*"],
  audit: true,  // enabled by default
});

policy.check("read_file", { user: "agent-1", session: "abc" });
policy.check("rm_important_data", { user: "agent-1", session: "abc" });

for (const entry of policy.auditLog) {
  console.log(`${entry.tool} → ${entry.decision} (${entry.reason})`);
  // read_file → allow (allowed by pattern: *)
  // rm_important_data → deny (denied by pattern: rm_*)
}

Policy as Code: Serialize and Version Control

Policies can be serialized to JSON and loaded from config files. This means you can version-control your security policies alongside your code:

// Export policy to JSON
const dict = policy.toDict();
fs.writeFileSync("policy.json", JSON.stringify(dict, null, 2));

// Load policy from JSON
const loaded = JSON.parse(fs.readFileSync("policy.json", "utf-8"));
const restoredPolicy = Policy.fromDict(loaded);

Python + Node.js Parity

The TypeScript Policy engine has full feature parity with the Python version:

FeaturePython SDKNode.js SDK
Allow/Deny/RequireApproval✅ v0.3.0✅ v0.2.0
Glob wildcards (* and ?)
Deny-by-default
Approval callbacks
Full audit trail
enforce() with exceptions
Serialization (toDict/fromDict)
Zero dependencies
Full type annotations
Test coverage96 tests53 tests

Integrating with Your Agent Framework

The Policy engine works with any TypeScript agent framework. Here's a pattern for the Vercel AI SDK:

import { Policy } from "tokenfence";
import { generateText, tool } from "ai";

const policy = new Policy({
  allow: ["searchWeb", "readFile"],
  deny: ["deleteFile", "executeCode"],
  requireApproval: ["sendEmail"],
});

// Wrap your tool execution
async function safeToolCall(toolName: string, args: any) {
  const result = policy.enforce(toolName);  // throws if denied

  if (result.needsApproval) {
    const approved = await requestHumanApproval(toolName, args);
    if (!approved) throw new Error("Tool call not approved");
  }

  return executeOriginalTool(toolName, args);
}

Get Started

npm install tokenfence

The full API reference is in the docs. The source is at github.com/u4ma-kev/tokenfence-node.

TokenFence v0.2.0: cost circuit breaker + least-privilege policy engine. Because your AI agents should have budgets and boundaries.

Ready to protect your AI budget?

Two lines of code. Per-workflow budgets. Automatic model downgrade. Hard kill switch.