Back to Activepieces

Network Security

docs/install/architecture/network-security.mdx

0.86.05.8 KB
Original Source

Overview

Activepieces makes outbound HTTP from two surfaces, and each is hardened separately:

  1. User code in flows: Code steps, piece actions, anything a flow author can write. Hardened by AP_NETWORK_MODE; see User-code egress.
  2. Server-side HTTP from the API: OAuth token claim and refresh, Vault, Conjur, event-destination webhooks, on-call pager, MCP tool validation. Always filtered, tuned via AP_SSRF_ALLOW_LIST; see Server-side egress.

Both surfaces block the same set of IPs (RFC1918 private, loopback, link-local, cloud-metadata, non-unicast) and share the AP_SSRF_ALLOW_LIST allow-list.

User-code egress

Every flow eventually runs user-supplied code: Code steps, piece actions, HTTP requests. Without an explicit boundary, that code can reach anything the host can reach: 127.0.0.1, Redis, Postgres, the Kubernetes API, cloud-metadata endpoints (169.254.169.254), the VPC. AP_NETWORK_MODE is the switch that controls this boundary.

<Tip> `AP_NETWORK_MODE` defaults to `UNRESTRICTED`. Set it to `STRICT` to install the in-process SSRF guard described below. </Tip>
ValueEffect
UNRESTRICTEDNo outbound guard. User code can reach any host the worker can reach.
STRICTThe engine SSRF guard is installed. Outbound connections to private, loopback, link-local, and cloud-metadata IPs are blocked from user code.

Related env var:

  • AP_SSRF_ALLOW_LIST: comma-separated IPs/CIDRs that bypass the block (for example an internal DB or sidecar). Shared with server-side egress.

How Isolation Works

In STRICT mode the engine installs an in-process SSRF guard before any user code runs. It monkey-patches Node's dns.lookup and Socket.prototype.connect:

  • DNS resolution: when user code resolves a hostname, the guard resolves every A/AAAA record and rejects the lookup if any of them falls in a blocked range. This closes the multi-record bypass where one IP is public and another is private.
  • Raw socket connect: when user code opens a socket directly to an IP, the guard checks that IP against the same blocklist.

Together these cover axios, fetch, undici, and raw http/net in a single pass. A blocked target throws SSRFBlockedError. The blocklist is every non-unicast range (RFC1918, loopback, link-local, multicast, cloud metadata), minus anything in AP_SSRF_ALLOW_LIST.

<Warning> The engine SSRF guard is **best-effort, in-process protection**. It reliably stops *accidental* SSRF, such as a flow or piece that naively follows a user-supplied URL to an internal host. It is **not** a hard boundary against deliberately malicious code: `worker_threads`, `child_process`, native addons, and `process.binding` can all sidestep the JavaScript-level monkey-patches.

For multi-tenant deployments that run untrusted code, enforce the real egress boundary in infrastructure: a VPC firewall, network policy, or egress gateway that blocks the cloud-metadata IP (169.254.169.254) and your private ranges independently of the application. Treat AP_NETWORK_MODE=STRICT as defense-in-depth on top of that, not as a replacement for it. </Warning>

The guard is independent of the sandbox execution mode. It is installed in every mode when AP_NETWORK_MODE=STRICT. See Sandboxing for what each sandbox mode isolates at the process level. Network security is independent: it constrains what code is allowed to reach, regardless of how it runs.

Verifying Your Setup

From a Code step in a test flow, try:

js
const res = await fetch('http://169.254.169.254/latest/meta-data/')

With AP_NETWORK_MODE=STRICT you should see an SSRFBlockedError. With UNRESTRICTED the request succeeds if the host allows it, confirming the guard is off.

Rolling Out

  1. Start in UNRESTRICTED (default) and identify any internal services that legitimate flows need to reach (internal APIs, databases used by Code steps, and so on).
  2. Add those IPs to AP_SSRF_ALLOW_LIST.
  3. Switch to AP_NETWORK_MODE=STRICT. Watch worker logs for SSRFBlockedError. Each one is either an attack, a misconfigured flow, or a missing allow-list entry.

Server-side egress

Separate from flow code, the API server itself makes outbound HTTP on behalf of admins and users: OAuth token claim and refresh, Hashicorp Vault, CyberArk Conjur, event-destination webhooks, on-call pager, MCP tool validation. The URLs come from admin config (Vault server URL) or user input (webhook destination, MCP server URL), so the same SSRF risks apply.

Unlike user-code egress, this layer is always on. It does not require AP_NETWORK_MODE=STRICT. It is implemented as a request-filtering-agent wrapper attached to the shared axios instances in @activepieces/server-utils, and every outbound request flows through it. The blocked ranges are identical to the engine guard (RFC1918, loopback, link-local, cloud metadata, non-unicast).

<Tip> `AP_SSRF_ALLOW_LIST` is shared between both surfaces. Add an IP or CIDR once and it applies to user-code egress **and** server-side HTTP. Restart the server after changing the value. </Tip>

When a request is blocked, the axios error surfaced in the admin UI includes the AP_SSRF_ALLOW_LIST hint, so operators see the fix directly in connection-test dialogs.

Self-hosted providers on private IPs

If Vault, Conjur, an on-prem OAuth2 token endpoint, or an internal webhook resolves to a private IP, the server-side filter rejects it until you add the target to AP_SSRF_ALLOW_LIST:

AP_SSRF_ALLOW_LIST=10.0.5.12,192.168.10.0/24

Relaxed TLS is still filtered

Connectors that accept self-signed certs (for example CyberArk Conjur in a private cluster) use rejectUnauthorized: false. The SSRF filter still applies under this setting: TLS verification is relaxed, SSRF protection is not.