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:
const PERMISSION_TIERS = {// Green: AI can execute freelyread: ['list_objects', 'get_object', 'list_templates','get_template', 'search_objects', 'get_balance','list_webhooks', 'get_sequencer_status'],// Yellow: AI can execute with rate limitslimited: ['mint_object', 'update_properties','create_webhook', 'send_notification'],// Red: Requires human approvalrestricted: ['transfer_object', 'burn_object','delete_template', 'create_api_key','bulk_transfer', 'update_template'],// Black: AI can never executeforbidden: ['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:
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 entriesconst 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:
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 adminawait 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:
const rateLimiter = new RateLimiter();const approvalQueue = new ApprovalQueue();const auditLog = [];async function safeExecute(operation, params, aiContext) {// 1. Check if forbiddenif (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 operationsif (!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 operationsif (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. Executeconst 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:
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.