Skip to main content

Path & SSRF Hardening

Agents that accept paths or URLs from the LLM (which got them from a user) are a classic source of security holes:
  • Path traversal: the LLM helpfully passes ../../etc/passwd to your read_file tool.
  • SSRF (Server-Side Request Forgery): the LLM fetches http://localhost:9200/_search and exfiltrates your internal Elasticsearch index.
  • Null byte injection: legit.txt\0/etc/shadow — some filesystems still trim at the null.
  • Control character injection: ANSI escape sequences or carriage returns in paths.
v2.0 ships hardened defaults plus building blocks for your own tools.

safeJoin(base, rel)

What it blocks

What it does NOT do

  • Does not follow symlinks. If users/2024.json is a symlink to /etc/passwd, safeJoin returns the symlink path; reading it will still escape. Use fs.realpath() after safeJoin if you need symlink-aware safety.
  • Does not check existence. Pure path math.
  • Does not normalize Unicode. Use path.normalize() upstream if you need NFC normalization.

PathSecurityError

Pattern-match on err instanceof PathSecurityError for fine-grained error handling.

Where it’s used by default

The FileSystemToolkit is now hardened automatically:
Any tool call with a traversal-y path (../../etc/passwd) raises PathSecurityError, which the tool executor catches and surfaces to the model as a clean error string. The model can self-correct. For tools you write yourself, route every LLM-supplied path through safeJoin:

SSRF protection — allowedHosts

URL-fetching toolkits accept an allowedHosts option that whitelists exactly which hosts the toolkit is permitted to reach.

Built-in coverage

ScraperToolkit is the canonical example; it accepts an LLM-supplied URL:
The model can now only successfully fetch from those three domains. Anything else throws PathSecurityError("Host blocked by allowedHosts policy: ...") before any network round-trip.

Matching rules

Sub-domain matching uses suffix comparison after a . boundary, so "example.com" does not accidentally allow notexample.com.

isHostAllowed / assertHostAllowed

Use these directly in your own URL-accepting tools:
isHostAllowed(url, undefined) and isHostAllowed(url, []) always return true (no restriction). Pass an explicit array to enforce.

Defense-in-depth recommendations

Even with safeJoin and allowedHosts, multiple layers help:
  1. Run the agent as an unprivileged OS user — even if pathing escapes, the user can’t read sensitive files.
  2. Restrict outbound network at the firewallallowedHosts is a soft fence; iptables / VPC egress rules are the hard fence.
  3. Audit tool.result events — the EventBus emits every tool call; alert on suspicious patterns (many denied requests in a short window).
  4. Use the SandboxAgent for any agent that runs arbitrary code or shell.

See also