This guide covers the security features available in the Perl SignalWire AI Agents SDK for SWML-based services (SWML -- SignalWire Markup Language -- is the JSON document format that defines agent behavior during calls).
Security is controlled through environment variables and constructor parameters, with secure defaults: HTTP basic auth is enabled by default with auto-generated credentials, webhook signatures are validated when a signing key is configured, and outbound URLs are validated against private/internal addresses.
To serve HTTPS directly:
export SWML_SSL_ENABLED=true
export SWML_SSL_CERT_PATH=/path/to/cert.pem
export SWML_SSL_KEY_PATH=/path/to/key.pemBasic authentication is enabled by default with auto-generated credentials. To set custom credentials:
export SWML_BASIC_AUTH_USER=myusername
export SWML_BASIC_AUTH_PASSWORD=mysecurepassword| Variable | Default | Description |
|---|---|---|
SWML_SSL_ENABLED |
false |
Enable HTTPS (true, 1, yes to enable) |
SWML_SSL_CERT_PATH |
- | Path to the SSL certificate file |
SWML_SSL_KEY_PATH |
- | Path to the SSL private key file |
HTTPS is served directly only when SWML_SSL_ENABLED is truthy AND both cert and
key paths resolve. Alternatively, passing ssl_cert and ssl_key to
run/serve always enables TLS.
The SDK's outbound clients verify server certificates by default. To trust a custom / private CA (e.g. an internal proxy or a self-signed test CA), point the matching transport at a PEM CA bundle via its env var — the two fleet-standard names, spelled exactly:
| Variable | Transport | Description |
|---|---|---|
SIGNALWIRE_REST_CA_FILE |
REST HTTP client | CA bundle used as the TLS trust root for SignalWire::REST requests |
SIGNALWIRE_RELAY_CA_FILE |
RELAY WebSocket client | CA bundle used as the TLS trust root for the SignalWire::Relay::Client connection |
When unset, each client uses the system trust store (and SSL_CERT_FILE, per
IO::Socket::SSL). These are the trust root only — verification stays ON; they
do not disable certificate checking.
| Variable | Default | Description |
|---|---|---|
SWML_BASIC_AUTH_USER |
auto-generated | Basic auth username |
SWML_BASIC_AUTH_PASSWORD |
auto-generated | Basic auth password |
When not set, both the username and password are auto-generated and printed at startup.
| Variable | Default | Description |
|---|---|---|
SIGNALWIRE_SIGNING_KEY |
- | Key used to validate incoming webhook signatures |
| Variable | Default | Description |
|---|---|---|
SWML_ALLOW_PRIVATE_URLS |
false |
When truthy (1, true, yes), allow outbound requests to private/internal addresses |
| Variable | Default | Description |
|---|---|---|
SWML_PROXY_URL_BASE |
- | Public base URL when the agent is behind a reverse proxy (used for webhook URL generation) |
SWML-based agents pick up the security configuration automatically:
use lib 'lib';
use SignalWire::Agent::AgentBase;
my $agent = SignalWire::Agent::AgentBase->new(
name => 'secure-agent',
route => '/agent',
);
# Serves HTTPS if SWML_SSL_ENABLED=true (and cert/key resolve),
# or pass ssl_cert/ssl_key explicitly.
$agent->run;Serving HTTPS with explicit certificate paths:
$agent->run(
host => '0.0.0.0',
port => 443,
ssl_cert => '/etc/ssl/certs/server.crt',
ssl_key => '/etc/ssl/private/server.key',
);Credentials are compared using a timing-safe comparison (HMAC-based) to mitigate timing attacks. You can read the active credentials from the agent:
my $user = $agent->basic_auth_user;
my $pass = $agent->basic_auth_password;When SIGNALWIRE_SIGNING_KEY is set (or a signing_key is passed to the agent),
incoming webhook requests are validated against the X-SignalWire-Signature
header (with X-Twilio-Signature accepted as a fallback). Requests with an
invalid signature are rejected.
The agent can issue and validate short-lived signed tokens for individual SWAIG
tools, requiring a configured signing_key (or SIGNALWIRE_SIGNING_KEY):
# Issue a token for a tool
my $token = $agent->create_tool_token('get_time', $call_id);
# Validate it later
my $ok = $agent->validate_tool_token('get_time', $token, $call_id);Outbound URLs (for example webhook destinations) are validated against
private/internal address ranges. By default, requests to private addresses are
rejected. Set SWML_ALLOW_PRIVATE_URLS=true to allow them (useful for local
development or internal-only deployments).
For production, serve HTTPS with valid certificates or terminate TLS at a reverse
proxy and set SWML_PROXY_URL_BASE:
export SWML_SSL_ENABLED=true
export SWML_SSL_CERT_PATH=/etc/ssl/certs/server.crt
export SWML_SSL_KEY_PATH=/etc/ssl/private/server.keyAlways set strong credentials in production:
export SWML_BASIC_AUTH_USER=api_user
export SWML_BASIC_AUTH_PASSWORD=$(openssl rand -base64 32)Set SIGNALWIRE_SIGNING_KEY so incoming webhooks are signature-verified, and so
SWAIG tool tokens can be issued and validated.
- Use certificates from a trusted CA in production.
- For development, generate a self-signed certificate:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes-
Check the file paths exist and are readable:
ls -la "$SWML_SSL_CERT_PATH" "$SWML_SSL_KEY_PATH"
-
Verify certificate validity:
openssl x509 -in "$SWML_SSL_CERT_PATH" -text -noout -
Confirm the key and certificate match:
openssl x509 -noout -modulus -in "$SWML_SSL_CERT_PATH" | openssl md5 openssl rsa -noout -modulus -in "$SWML_SSL_KEY_PATH" | openssl md5
- If you did not set custom credentials, look for the auto-generated values in the startup output.
- Test with curl:
curl -u username:password http://localhost:3000/
If a webhook or outbound request to an internal host is being rejected, allow private URLs explicitly (only when you trust the destinations):
export SWML_ALLOW_PRIVATE_URLS=trueBefore deploying to production:
- HTTPS enabled with valid certificates (or TLS terminated at a trusted proxy)
- Strong basic auth credentials set
-
SIGNALWIRE_SIGNING_KEYconfigured for webhook signature validation -
SWML_ALLOW_PRIVATE_URLSleft disabled unless internal destinations are required - SSL certificate expiration monitored
For environment-variable and constructor configuration details, see the Configuration Guide.