AI Safety & Guardrails for Token Operations

Implement validation layers, rate limiting, and approval workflows to safely let AI agents manage tokens.

What You'll Build

AI agents are powerful, but power without guardrails is dangerous. An unchecked LLM could transfer all your tokens, delete templates, or burn through your API rate limits. In this tutorial you'll build a safety layer that wraps Dual API calls with validation, rate limiting, human-in-the-loop approval, and audit logging, ensuring AI agents operate within safe boundaries.

Step 1, Define Permission Tiers

Categorize every Dual operation by risk level:

javascript
const PERMISSION_TIERS = {
// Green: AI can execute freely
read: [
'list_objects', 'get_object', 'list_templates',
'get_template', 'search_objects', 'get_balance',
'list_webhooks', 'get_sequencer_status'
],
// Yellow: AI can execute with rate limits
limited: [
'mint_object', 'update_properties',
'create_webhook', 'send_notification'
],
// Red: Requires human approval
restricted: [
'transfer_object', 'burn_object',
'delete_template', 'create_api_key',
'bulk_transfer', 'update_template'
],
// Black: AI can never execute
forbidden: [
'delete_organization', 'change_owner',
'reset_api_keys', 'modify_roles'
]
};

Step 2, Build the Rate Limiter

Prevent AI agents from making too many calls in a short period:

javascript
class RateLimiter {
constructor() {
this.windows = new Map();
}
check(operation, limits = { perMinute: 30, perHour: 200 }) {
const now = Date.now();
const key = operation;
if (!this.windows.has(key)) {
this.windows.set(key, []);
}
const calls = this.windows.get(key);
// Clean old entries
const minuteAgo = now - 60000;
const hourAgo = now - 3600000;
const recentMinute = calls.filter(t => t > minuteAgo);
const recentHour = calls.filter(t => t > hourAgo);
if (recentMinute.length >= limits.perMinute) {
return { allowed: false, reason: 'Rate limit: too many calls per minute' };
}
if (recentHour.length >= limits.perHour) {
return { allowed: false, reason: 'Rate limit: too many calls per hour' };
}
calls.push(now);
this.windows.set(key, calls.filter(t => t > hourAgo));
return { allowed: true };
}
}

Step 3, Human-in-the-Loop Approval

For restricted operations, pause execution and ask for confirmation:

javascript
class ApprovalQueue {
constructor() {
this.pending = new Map();
}
async requestApproval(operation, params, context) {
const id = crypto.randomUUID();
const request = {
id, operation, params, context,
requestedAt: new Date().toISOString(),
status: 'pending'
};
this.pending.set(id, request);
// Send notification to admin
await sendAdminNotification({
title: \"AI Agent Approval Request\",
body: \"Operation: \" + operation + "\\nParams: \" + JSON.stringify(params) + "\\nContext: \" + context + "\",
approval_id: id
});
return { approval_id: id, status: 'pending' };
}
approve(id) {
const req = this.pending.get(id);
if (req) { req.status = 'approved'; return true; }
return false;
}
deny(id, reason) {
const req = this.pending.get(id);
if (req) { req.status = 'denied'; req.reason = reason; return true; }
return false;
}
}

Step 4, The Guardrail Wrapper

Wrap every AI tool call through the safety layer:

javascript
const rateLimiter = new RateLimiter();
const approvalQueue = new ApprovalQueue();
const auditLog = [];
async function safeExecute(operation, params, aiContext) {
// 1. Check if forbidden
if (PERMISSION_TIERS.forbidden.includes(operation)) {
auditLog.push({ operation, status: 'blocked', reason: 'forbidden' });
return { error: 'This operation is not available to AI agents.' };
}
// 2. Check rate limits for non-read operations
if (!PERMISSION_TIERS.read.includes(operation)) {
const rateCheck = rateLimiter.check(operation);
if (!rateCheck.allowed) {
auditLog.push({ operation, status: 'rate_limited' });
return { error: rateCheck.reason };
}
}
// 3. Require approval for restricted operations
if (PERMISSION_TIERS.restricted.includes(operation)) {
const approval = await approvalQueue.requestApproval(
operation, params, aiContext
);
auditLog.push({ operation, status: 'awaiting_approval', id: approval.approval_id });
return { pending: true, message: 'Awaiting human approval', ...approval };
}
// 4. Execute
const result = await executeOperation(operation, params);
auditLog.push({
operation, status: 'executed',
timestamp: new Date().toISOString(), params
});
return result;
}

Step 5, Audit Dashboard

Expose the audit log so you can review what the AI agent has been doing:

javascript
app.get('/admin/audit', (req, res) => {
const { operation, status, since } = req.query;
let logs = [...auditLog];
if (operation) logs = logs.filter(l => l.operation === operation);
if (status) logs = logs.filter(l => l.status === status);
if (since) logs = logs.filter(l => new Date(l.timestamp) > new Date(since));
res.json({
total: logs.length,
logs: logs.slice(-100) // Last 100 entries
});
});

Defense in Depth: These client-side guardrails are your first line of defense, but always combine them with server-side rate limits and Dual's built-in permission system. Use scoped API keys that only grant the permissions the AI actually needs.

Companion Repo: Get the full working source code for this tutorial at github.com/orgs/DualOrg/dual-ai-guardrails, clone it, add your API keys, and run it locally in minutes.

Summary

You've built a comprehensive safety layer for AI-powered token operations: permission tiers prevent dangerous calls, rate limiting stops runaway agents, human-in-the-loop approval catches high-risk operations, and audit logging provides full visibility. For more on the Dual permission model, see Organization Roles & Permissions.