docs/install/architecture/network-security.mdx
Activepieces makes outbound HTTP from two surfaces, and each is hardened separately:
AP_NETWORK_MODE; see User-code egress.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.
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.
| Value | Effect |
|---|---|
UNRESTRICTED | No outbound guards. User code can reach any host the worker can reach. Default while the hardening stack is being validated. |
STRICT | All three layers below are installed. Outbound connections to private, loopback, link-local, and cloud-metadata IPs are blocked from user code. |
Related env vars:
AP_SSRF_ALLOW_LIST — comma-separated IPs that bypass the block (e.g. an internal DB or sidecar).HTTP_PROXY / HTTPS_PROXY — if set, the engine routes all outbound HTTP(S) through the proxy. Loopback proxy ports are auto-exempted.In STRICT mode, three layers stack. Each one is a tripwire — if a request slips past one, the next one stops it.
flowchart LR
UC["User code
(Code step · Piece · fetch)"]
subgraph L1["Layer 1 — Engine SSRF guard (in-process)"]
direction TB
L1check{"IP in blocked
range?"}
end
subgraph L2["Layer 2 — Egress proxy (loopback)"]
direction TB
L2check{"Any A/AAAA
record blocked?"}
end
subgraph L3["Layer 3 — iptables lockdown (SANDBOX_PROCESS only)"]
direction TB
L3check{"Destination is
proxy port?"}
end
NET(["Public internet"])
B1["SSRFBlockedError"]
B2["403 Egress blocked"]
B3["Kernel drops packet"]
UC --> L1check
L1check -- yes --> B1
L1check -- no --> L2check
L2check -- yes --> B2
L2check -- no --> L3check
L3check -- no --> B3
L3check -- yes --> NET
classDef blocked fill:#fde2e2,stroke:#d33,color:#7a1414
classDef allowed fill:#e3f7e4,stroke:#2a7,color:#14532d
class B1,B2,B3 blocked
class NET allowed
Network Security layers on top of the sandbox execution mode — they are independent choices. The table below shows what is active in STRICT mode for each sandbox:
| Sandbox Mode | Engine SSRF guard | Egress proxy | Kernel lockdown |
|---|---|---|---|
No Sandboxing (UNSANDBOXED) | ✅ | ✅ | ❌ — no per-UID separation available |
V8 Sandboxing (SANDBOX_CODE_ONLY) | ✅ | ✅ | ❌ — engine process shares the host UID |
Kernel Namespaces (SANDBOX_PROCESS with isolate) | ✅ | ✅ | ✅ — iptables rules scoped to sandbox UIDs |
See Sandboxing for what each sandbox mode isolates at the process level. Network Security is orthogonal: it isolates what the sandbox is allowed to reach, regardless of how it runs.
<Warning> In `UNSANDBOXED` and V8 modes there is no kernel-level fallback. The in-process SSRF guard and the egress proxy are the only two layers — which is still strong, but a determined attacker who compromises the Node process can bypass in-process checks. Use `SANDBOX_PROCESS` in multi-tenant deployments. </Warning>From a Code step in a test flow, try:
const res = await fetch('http://169.254.169.254/latest/meta-data/')
With AP_NETWORK_MODE=STRICT you should see an SSRFBlockedError (from the engine guard) or a 403 from the egress proxy (for hostname-resolved requests). With UNRESTRICTED the request will succeed if the host allows it — confirming the guard is off.
UNRESTRICTED (default) and identify any internal services that legitimate flows need to reach (internal APIs, databases used by Code steps, etc.).AP_SSRF_ALLOW_LIST.AP_NETWORK_MODE=STRICT. Watch worker logs for SSRFBlockedError or Egress proxy refused request — each one is either an attack, a misconfigured flow, or a missing allow-list entry.Separate from flow code, the API server itself makes outbound HTTP on behalf of admins and users — OAuth token claim/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; every outbound request flows through it. The blocked ranges are identical to the engine guard (RFC1918, loopback, link-local / cloud metadata, non-unicast).
When a request is blocked, the axios error surfaced in the admin UI includes the AP_SSRF_ALLOW_LIST hint, so operators see the remediation directly in connection-test dialogs.
If Vault, Conjur, an on-prem OAuth2 token endpoint, or an internal webhook resolves to a private IP, the server-side filter will reject it until you add the target to AP_SSRF_ALLOW_LIST:
AP_SSRF_ALLOW_LIST=10.0.5.12,192.168.10.0/24
Connectors that accept self-signed certs (e.g. CyberArk Conjur in a private cluster) use rejectUnauthorized: false. The SSRF filter is preserved under this setting — TLS verification is relaxed, SSRF protection is not.