Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SSRF Guard

Middleware that prevents Server-Side Request Forgery (SSRF) attacks by inspecting tool call arguments for URLs pointing to private, loopback, or otherwise restricted addresses.

Why Agents Need SSRF Protection

AI agents that execute tool calls based on LLM output are vulnerable to SSRF. A malicious prompt or injected instruction can trick the model into making requests to internal services -- fetching cloud metadata endpoints (e.g. AWS 169.254.169.254), scanning private networks, or exfiltrating data through internal APIs. The SSRF guard inspects every tool call before execution, blocking URLs that resolve to dangerous destinations.

Constructor

use synaptic::middleware::{SsrfGuardMiddleware, SsrfGuardConfig};

let mw = SsrfGuardMiddleware::new(SsrfGuardConfig::default());

The default configuration blocks all private/loopback addresses and scans the standard URL argument keys.

SsrfGuardConfig

Customize the guard behavior through SsrfGuardConfig:

use std::collections::HashSet;
use synaptic::middleware::SsrfGuardConfig;

let config = SsrfGuardConfig {
    block_private: true,
    blocklist: HashSet::from(["evil.com".to_string()]),
    allowlist: HashSet::new(),
    url_keys: vec![
        "url".to_string(),
        "uri".to_string(),
        "endpoint".to_string(),
        "base_url".to_string(),
        "webhook_url".to_string(),
    ],
};

Fields:

  • block_private -- When true (the default), blocks URLs pointing to private, loopback, and link-local addresses.
  • blocklist -- Additional hostnames that should always be blocked, even if they resolve to public addresses.
  • allowlist -- Hostnames that are always allowed, overriding block_private for those specific hosts.
  • url_keys -- Tool argument keys that are inspected for URLs. The middleware also recursively scans nested objects and checks any string value that looks like a URL.

What Gets Blocked

When block_private is enabled, the following are blocked:

  • Loopback: localhost, 127.0.0.1, ::1
  • Private IPv4: 10.x.x.x, 172.16.x.x -- 172.31.x.x, 192.168.x.x
  • Link-local: 169.254.x.x, fe80::/10
  • Cloud metadata: 169.254.169.254 (AWS/GCP metadata endpoint)
  • Private hostnames: *.local, *.internal, metadata.google.internal
  • CGNAT range: 100.64.0.0/10
  • Unspecified: 0.0.0.0, ::
  • Broadcast: 255.255.255.255
  • IPv6 unique local: fc00::/7

Public URLs (e.g. https://api.openai.com) pass through without interference.

Usage with create_agent

use std::sync::Arc;
use synaptic::graph::{create_agent, AgentOptions};
use synaptic::middleware::{SsrfGuardMiddleware, SsrfGuardConfig};

let options = AgentOptions {
    middleware: vec![
        Arc::new(SsrfGuardMiddleware::new(SsrfGuardConfig::default())),
    ],
    ..Default::default()
};

let graph = create_agent(model, tools, options)?;

How It Works

  • Lifecycle hook: wrap_tool_call
  • Before each tool call is executed, the middleware scans the tool's JSON arguments.
  • It checks keys listed in url_keys for URL strings, and also recurses into nested objects and arrays.
  • Any standalone string value starting with http:// or https:// is also checked.
  • If a URL's host is on the blocklist or resolves to a private/restricted address, the middleware returns a SynapticError::Security error and the tool call is never executed.
  • If the host is on the allowlist, it passes regardless of private IP rules.

Allowlist Example

Allow a specific internal endpoint while blocking all other private addresses:

use std::collections::HashSet;
use std::sync::Arc;
use synaptic::graph::{create_agent, AgentOptions};
use synaptic::middleware::{SsrfGuardMiddleware, SsrfGuardConfig};

let mut config = SsrfGuardConfig::default();
config.allowlist.insert("internal-api.company.local".to_string());

let options = AgentOptions {
    middleware: vec![
        Arc::new(SsrfGuardMiddleware::new(config)),
    ],
    ..Default::default()
};

let graph = create_agent(model, tools, options)?;

With this configuration, http://internal-api.company.local/status is allowed, but http://localhost/admin and http://192.168.1.1/config remain blocked.

Blocklist Example

Block a known-malicious public domain in addition to private addresses:

let mut config = SsrfGuardConfig::default();
config.blocklist.insert("evil.com".to_string());
config.blocklist.insert("phishing-site.net".to_string());

Configuration Reference

FieldTypeDefaultDescription
block_privatebooltrueBlock private/loopback/link-local addresses
blocklistHashSet<String>{}Additional hostnames to block
allowlistHashSet<String>{}Hostnames that bypass private-address blocking
url_keysVec<String>["url", "uri", "endpoint", "base_url", "webhook_url"]Argument keys inspected for URLs

Combining with Other Middleware

The SSRF guard pairs well with other security and reliability middleware:

use std::sync::Arc;
use synaptic::graph::{create_agent, AgentOptions};
use synaptic::middleware::{
    SsrfGuardMiddleware, SsrfGuardConfig,
    CircuitBreakerMiddleware, CircuitBreakerConfig,
    ToolRetryMiddleware,
};

let options = AgentOptions {
    middleware: vec![
        Arc::new(SsrfGuardMiddleware::new(SsrfGuardConfig::default())),
        Arc::new(CircuitBreakerMiddleware::new(CircuitBreakerConfig::default())),
        Arc::new(ToolRetryMiddleware::new(3)),
    ],
    ..Default::default()
};

let graph = create_agent(model, tools, options)?;

The SSRF guard should generally be listed first so that blocked URLs are rejected before reaching retry or circuit breaker logic.