- Overview
- Authentication
- SSL/TLS
- Security Headers
- CORS
- Rate Limiting
- Host Validation
- Request Size Limits
- Secure Tools (HMAC Tokens)
- Production Checklist
The SignalWire AI Agents TypeScript SDK provides multiple layers of security, applied automatically or via configuration:
- Authentication -- Basic auth on all agent routes (always enabled), plus optional bearer token, API key, and custom validation via
AuthHandler. - SSL/TLS -- HTTPS with HSTS support via
SslConfig. - Security headers --
X-Content-Type-Options,X-Frame-Options,X-XSS-Protection, andReferrer-Policyon every response. - CORS -- Configurable origin restrictions via
SWML_CORS_ORIGINS. - Rate limiting -- Per-IP request throttling via
SWML_RATE_LIMIT. - Host validation -- Hostname allowlist via
SWML_ALLOWED_HOSTS. - Request size limits -- Maximum payload size via
SWML_MAX_REQUEST_SIZE. - Secure tools -- Per-function HMAC-signed tokens that bind tool calls to specific call sessions.
Every AgentBase instance applies Hono's basicAuth middleware to all routes (/, /swaig, /post_prompt, /debug_events). Credentials are resolved in the following order:
- Constructor option --
basicAuth: ['username', 'password']inAgentOptions. - Environment variables --
SWML_BASIC_AUTH_USERandSWML_BASIC_AUTH_PASSWORD(both must be set). - Auto-generated -- the agent name as the username and a random 16-character hex string as the password.
import { AgentBase } from '@signalwire/sdk';
// Explicit credentials
const agent = new AgentBase({
name: 'my-agent',
basicAuth: ['admin', 'hunter2'],
});
// Environment-based: set SWML_BASIC_AUTH_USER and SWML_BASIC_AUTH_PASSWORD
const agent2 = new AgentBase({ name: 'my-agent' });
// Auto-generated: credentials are logged at startup
const agent3 = new AgentBase({ name: 'my-agent' });
// Logs: Auth: my-agent:**** (source: generated)To inspect credentials and their source at runtime:
const [user, pass] = agent.getBasicAuthCredentials();
const [user2, pass2, source] = agent.getBasicAuthCredentials(true);
// source: 'provided' | 'environment' | 'generated'Override validateBasicAuth() in a subclass to add custom validation logic beyond credential matching:
class SecureAgent extends AgentBase {
validateBasicAuth(username: string, password: string): boolean {
// Block specific users
if (username === 'blocked') return false;
// Optionally query a database, check IP allowlists, etc.
return true;
}
}For advanced use cases beyond basic auth, AuthHandler (src/AuthHandler.ts) supports multiple authentication methods with timing-safe credential comparison to prevent timing attacks:
import { AuthHandler } from '@signalwire/sdk';
const auth = new AuthHandler({
// Method 1: Bearer token (Authorization: Bearer <token>)
bearerToken: 'my-secret-token',
// Method 2: API key (X-Api-Key: <key>)
apiKey: 'my-api-key-123',
// Method 3: Basic auth (Authorization: Basic <base64>)
basicAuth: ['admin', 'password'],
// Method 4: Custom validator
customValidator: async (req) => {
return req.headers['x-internal-service'] === 'trusted';
},
});Validation order: Methods are checked in order (Bearer, API Key, Basic, Custom). The first successful match allows the request. If no methods are configured, all requests pass (backwards compatibility).
Using as Hono middleware:
const app = new Hono();
app.use('/api/*', auth.middleware());
// Returns 401 JSON response: { "error": "Unauthorized" }Timing-safe comparison: All credential checks use crypto.timingSafeEqual to prevent timing attacks. When string lengths differ, a dummy comparison is performed to avoid leaking length information.
The SslConfig class (src/SslConfig.ts) manages SSL/TLS configuration for HTTPS serving.
SSL can be configured via constructor options or environment variables:
import { SslConfig } from '@signalwire/sdk';
// Via constructor
const ssl = new SslConfig({
enabled: true,
certPath: '/etc/ssl/certs/agent.pem',
keyPath: '/etc/ssl/private/agent-key.pem',
domain: 'agent.example.com',
hsts: true, // default: true
hstsMaxAge: 31536000, // default: 31536000 (1 year)
});Or via environment variables:
| Variable | Description |
|---|---|
SWML_SSL_ENABLED |
Set to "true" to enable SSL. |
SWML_SSL_CERT_PATH |
Path to the PEM-encoded certificate file. |
SWML_SSL_KEY_PATH |
Path to the PEM-encoded private key file. |
SWML_SSL_DOMAIN |
Domain name for HSTS headers. |
Environment variables are used as fallbacks when constructor options are not provided.
| Property | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
false |
Whether SSL is enabled. |
certPath |
string |
-- | Path to the PEM certificate file. |
keyPath |
string |
-- | Path to the PEM private key file. |
domain |
string |
-- | Domain name for HSTS. |
hsts |
boolean |
true |
Whether to emit HSTS headers. |
hstsMaxAge |
number |
31536000 |
HSTS max-age value in seconds (1 year). |
// Check if SSL is fully configured (enabled + cert + key exist on disk)
if (ssl.isConfigured()) {
// Get Node.js https.createServer() options
const serverOpts = ssl.getServerOptions();
// { cert: '...', key: '...' }
}
// Get the HSTS header value
const hsts = ssl.getHstsHeader();
// "max-age=31536000; includeSubDomains" (or null if disabled)Apply HSTS headers to all responses via Hono middleware:
const ssl = new SslConfig({ enabled: true, certPath: '...', keyPath: '...' });
app.use('*', ssl.hstsMiddleware());
// Adds: Strict-Transport-Security: max-age=31536000; includeSubDomainsBoth AgentBase and AgentServer automatically apply the following security headers to every HTTP response:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents browsers from MIME-sniffing the response away from the declared content type. |
X-Frame-Options |
DENY |
Prevents the page from being embedded in frames (clickjacking protection). |
X-XSS-Protection |
1; mode=block |
Enables the browser's built-in XSS filter (legacy browsers). |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls how much referrer information is sent with requests. |
These headers are applied automatically via Hono middleware and require no configuration. They are set in both AgentBase.getApp() and AgentServer's constructor.
By default, the SDK uses a permissive CORS policy that allows all origins (*). This is suitable for development but should be restricted in production.
Set the SWML_CORS_ORIGINS environment variable to a comma-separated list of allowed origins:
export SWML_CORS_ORIGINS="https://app.example.com,https://admin.example.com"When set, only requests from the listed origins will receive CORS headers. All other cross-origin requests will be blocked by the browser.
CORS is applied via Hono's built-in cors() middleware with credentials: true:
// Internal implementation in AgentBase.getApp()
const corsOrigins = process.env['SWML_CORS_ORIGINS'];
const corsOrigin = corsOrigins ? corsOrigins.split(',').map(o => o.trim()) : '*';
app.use('*', cors({ origin: corsOrigin, credentials: true }));AgentServer uses origin: '*' by default. For multi-agent deployments, set SWML_CORS_ORIGINS when using standalone AgentBase instances.
Set the SWML_RATE_LIMIT environment variable to enable per-IP request throttling:
# Allow 60 requests per minute per IP address
export SWML_RATE_LIMIT=60- Tracking is per-IP, using
X-Forwarded-For(first IP),X-Real-IP, or"unknown"as fallback. - The counter resets every 60 seconds per IP.
- When the limit is exceeded, the server responds with HTTP
429:
{ "error": "Rate limit exceeded" }# Set a conservative limit for production
export SWML_RATE_LIMIT=120
# Or a very low limit for testing
export SWML_RATE_LIMIT=5Rate limiting is only enabled when SWML_RATE_LIMIT is set to a positive integer. When unset, no rate limiting is applied.
Set SWML_ALLOWED_HOSTS to restrict which Host header values are accepted:
export SWML_ALLOWED_HOSTS="agent.example.com,api.example.com"- The
Hostheader is extracted from each request, the port is stripped, and the hostname is compared (case-insensitive) against the allowlist. - If the hostname is not in the list, the server responds with HTTP
403:
{ "error": "Forbidden: host not allowed" }- When
SWML_ALLOWED_HOSTSis not set, host validation is disabled and all hostnames are accepted.
- Prevent DNS rebinding attacks.
- Ensure the agent only responds to requests addressed to known hostnames.
- Useful behind reverse proxies to block direct IP-based access.
Set SWML_MAX_REQUEST_SIZE to limit the maximum allowed request body size:
# Limit to 512 KB
export SWML_MAX_REQUEST_SIZE=524288- The
Content-Lengthheader is checked against the configured maximum. - Requests exceeding the limit receive HTTP
413:
{ "error": "Request too large" }- Default limit:
1048576bytes (1 MB).
The SessionManager (src/SessionManager.ts) provides stateless HMAC-SHA256 token generation and validation for SWAIG function calls. This ensures that tool invocations are cryptographically bound to specific call sessions.
- When
renderSwml()is called, a unique session token is generated for each function marked assecure: true. - The token is appended to the function's webhook URL as a
__tokenquery parameter. - When the platform invokes the function, the agent validates the token before executing the handler.
Tokens are base64url-encoded strings with the following internal structure:
base64url( callId.functionName.expiry.nonce.hmacSignature )
| Field | Description |
|---|---|
callId |
The call ID this token is bound to. |
functionName |
The SWAIG function name this token authorizes. |
expiry |
Unix timestamp (seconds) when the token expires. |
nonce |
Random 8-character hex string for uniqueness. |
hmacSignature |
First 16 hex characters of the HMAC-SHA256 signature over callId:functionName:expiry:nonce. |
agent.defineTool({
name: 'transfer_funds',
description: 'Transfer money between accounts',
parameters: {
type: 'object',
properties: {
amount: { type: 'number', description: 'Amount to transfer' },
toAccount: { type: 'string', description: 'Destination account ID' },
},
},
secure: true, // Enables HMAC token protection
handler: async (args, rawData) => {
// Only reachable with a valid, unexpired token
const result = new FunctionResult();
result.setResponse('Transfer completed.');
return result;
},
});- Generation:
sessionManager.generateToken(functionName, callId)creates a token with the configured expiry (default: 3600 seconds / 1 hour). - Validation:
sessionManager.validateToken(callId, functionName, token)verifies the HMAC signature, checks expiry, and confirms the call ID and function name match. - No server-side state: Tokens are self-contained. The server does not store token state; validation is purely cryptographic.
const agent = new AgentBase({
name: 'my-agent',
tokenExpirySecs: 7200, // 2 hours
});import { SessionManager } from '@signalwire/sdk';
const sm = new SessionManager(3600);
const token = sm.generateToken('my_function', 'call-123');
// Decode without validating (for debugging)
const info = sm.debugToken(token);
// { callId: 'call-123', functionName: 'my_function', expiry: 1737000000,
// nonce: 'a1b2c3d4', signature: '...', expired: false }SessionManager also provides per-session metadata storage:
const sm = new SessionManager();
sm.setSessionMetadata('session-1', { userId: 'u-42', plan: 'pro' });
const meta = sm.getSessionMetadata('session-1');
// { userId: 'u-42', plan: 'pro' }
sm.deleteSessionMetadata('session-1');Follow these best practices when deploying agents to production:
-
Set explicit basic-auth credentials -- Do not rely on auto-generated passwords. Use
basicAuthin the constructor or setSWML_BASIC_AUTH_USERandSWML_BASIC_AUTH_PASSWORDenvironment variables. -
Enable SSL/TLS -- Set
SWML_SSL_ENABLED=truewith validSWML_SSL_CERT_PATHandSWML_SSL_KEY_PATH, or terminate TLS at your reverse proxy / load balancer. -
Restrict CORS origins -- Set
SWML_CORS_ORIGINSto your specific frontend domains instead of allowing all origins. -
Enable rate limiting -- Set
SWML_RATE_LIMITto a reasonable value (e.g.,120requests/minute) to prevent abuse. -
Configure allowed hosts -- Set
SWML_ALLOWED_HOSTSto your expected domain names to block DNS rebinding and direct IP access. -
Review request size limits -- Adjust
SWML_MAX_REQUEST_SIZEif your use case requires larger or smaller payloads than the 1 MB default. -
Use secure tools for sensitive operations -- Mark tools that perform privileged actions (payments, transfers, data mutations) with
secure: true. -
Set the proxy URL -- If behind a reverse proxy, set
SWML_PROXY_URL_BASEto ensure webhook URLs use the correct external address. -
Reduce log verbosity -- Set
SIGNALWIRE_LOG_LEVEL=warnorerrorin production to avoid logging sensitive request data. -
Rotate secrets -- The HMAC signing key for
SessionManageris generated randomly on each process start. For multi-instance deployments, provide a shared secret key or use short token expiry times. -
Monitor health endpoints -- Use the unauthenticated
/healthand/readyendpoints for load balancer health checks.