2025-12-24 · Authensor

AI Agent Safety for Serverless Functions (Lambda, Vercel, Cloudflare Workers)

SafeClaw by Authensor provides deny-by-default action gating for AI agents running in serverless environments — AWS Lambda, Vercel Functions, and Cloudflare Workers. Each agent action is checked against your YAML policy before execution, even in ephemeral, stateless compute. Install with npx @authensor/safeclaw and deploy the gate alongside your function or as a separate microservice.

The Serverless Agent Safety Challenge

Serverless functions are stateless and ephemeral, but AI agents running inside them still have access to dangerous operations: /tmp filesystem writes, outbound HTTP, and (in Lambda) subprocess execution. The stateless nature makes audit logging harder — SafeClaw solves this with its hash-chained audit trail that persists decisions to external storage.

446 tests validate the gate engine. Works with Claude and OpenAI. MIT licensed.

Installation

For serverless, SafeClaw can run in two modes:

  1. Embedded — bundled into your function (higher cold start, no network hop)
  2. Sidecar service — deployed as a separate microservice (lower function size, network hop)
npx @authensor/safeclaw  # for sidecar mode
npm install @authensor/safeclaw  # for embedded mode

Policy

version: 1
defaultAction: deny

rules:
- action: file.write
path:
glob: "/tmp/**"
decision: allow

- action: file.read
path:
glob: "/tmp/**"
decision: allow

- action: network.request
host:
in: ["api.openai.com", "api.anthropic.com", "dynamodb.us-east-1.amazonaws.com"]
decision: allow

- action: process.exec
decision: deny # no shell execution in serverless

AWS Lambda Integration

// handler.js
import { Gate } from '@authensor/safeclaw';
import { readFile, writeFile } from 'fs/promises';

const gate = new Gate(); // embedded mode, reads policy from bundled YAML

export async function handler(event) {
const { action, params } = JSON.parse(event.body);

try {
switch (action) {
case 'readTempFile': {
const decision = await gate.check({
action: 'file.read',
path: params.path
});
if (!decision.allowed) {
return { statusCode: 403, body: JSON.stringify({ error: decision.reason }) };
}
const content = await readFile(params.path, 'utf-8');
return { statusCode: 200, body: JSON.stringify({ content }) };
}

case 'callApi': {
const host = new URL(params.url).hostname;
const decision = await gate.check({
action: 'network.request',
host,
url: params.url,
method: 'POST'
});
if (!decision.allowed) {
return { statusCode: 403, body: JSON.stringify({ error: decision.reason }) };
}
const response = await fetch(params.url, {
method: 'POST',
body: JSON.stringify(params.body),
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
return { statusCode: 200, body: JSON.stringify(data) };
}

default:
return { statusCode: 400, body: 'Unknown action' };
}
} catch (err) {
return { statusCode: 500, body: err.message };
}
}

Vercel Serverless Function

// api/agent.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { Gate } from '@authensor/safeclaw';

const gate = new Gate();

export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'POST') return res.status(405).end();

const { action, url } = req.body;

if (action === 'fetch') {
const host = new URL(url).hostname;
const decision = await gate.check({
action: 'network.request',
host,
url,
method: 'GET'
});
if (!decision.allowed) {
return res.status(403).json({ error: decision.reason });
}
const response = await fetch(url);
const data = await response.json();
return res.json(data);
}

res.status(400).json({ error: 'Unknown action' });
}

Cloudflare Workers

// worker.js
import { Gate } from '@authensor/safeclaw';

const gate = new Gate();

export default {
async fetch(request, env) {
const { action, url } = await request.json();

if (action === 'fetch') {
const host = new URL(url).hostname;
const decision = await gate.check({
action: 'network.request',
host,
url,
method: 'GET'
});
if (!decision.allowed) {
return new Response(JSON.stringify({ error: decision.reason }), {
status: 403,
headers: { 'Content-Type': 'application/json' }
});
}
return fetch(url);
}

return new Response('Unknown action', { status: 400 });
}
};

Audit Persistence

In serverless, pipe audit logs to external storage:

# safeclaw.config.yaml
audit:
  sink: dynamodb  # or s3, cloudwatch, webhook
  table: safeclaw-audit
  region: us-east-1

Every decision is hash-chained regardless of sink. MIT licensed, provider-agnostic.

Cross-References

Try SafeClaw

Action-level gating for AI agents. Set it up in your browser in 60 seconds.

$ npx @authensor/safeclaw