@n8n/backend-network 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,7 +39,7 @@ const data = await client.request({ method: 'GET', url: 'https://api.example.com
39
39
  ```
40
40
 
41
41
  `HttpRequestClientOptions` carry policy that applies to every call on the
42
- client — SSRF level, a `baseURL`, and default `headers`. Set them once at
42
+ client — the `useDefaultSsrfPolicy`, a `baseURL`, and default `headers`. Set them once at
43
43
  creation instead of repeating them per request:
44
44
 
45
45
  ```ts
@@ -58,70 +58,82 @@ dictated by what the consuming library accepts.
58
58
 
59
59
  ## SSRF protection
60
60
 
61
- ### On by default
61
+ ### Safe by default
62
62
 
63
- Both `requests()` and `transport()` apply the container's
64
- `SsrfProtectionService` **unless you explicitly opt out** with `ssrf: 'disabled'`.
63
+ Both `requests()` and `transport()` guard every call — and every redirect hop —
64
+ by default. You do not pass a bridge, a service, or a config flag; the default
65
+ `useDefaultSsrfPolicy: 'safe'` is applied for you. The only way to skip the guard is to
66
+ opt out explicitly with `useDefaultSsrfPolicy: 'unsafe'`.
65
67
 
66
68
  This default is deliberate. Outbound HTTP in n8n is frequently driven by
67
69
  user-controlled input — credential URLs, workflow parameters, redirect targets
68
70
  returned by a remote server. Without guarding, that input can be pointed at
69
71
  internal-only addresses (cloud metadata endpoints, `localhost`, private ranges),
70
72
  turning the n8n backend into a confused deputy (a Server-Side Request Forgery,
71
- or SSRF). Centralizing the call here means every backend request — and every
72
- redirect hop — is validated by default, so a new call site is safe unless
73
- someone consciously turns the guard off.
74
-
75
- Because the secure default lives in the factory rather than at each call site,
76
- forgetting to add protection cannot silently introduce a vulnerability — the
77
- unsafe choice is the explicit one.
78
-
79
- ### Why the choice is explicit per call, not read from config
80
-
81
- The `ssrf` option is passed **per call**; it is **not** derived automatically
82
- from `SsrfProtectionConfig`. That is intentional: whether a request is dangerous
83
- is a property of *its destination*, and only the calling code knows that. A
84
- global flag cannot tell a fixed, n8n-owned URL apart from a URL a user just
85
- pasted into a credential. So each call site makes a single, local, reviewable
86
- decision — and a reviewer reading a `ssrf: 'disabled'` knows to ask "is this
87
- destination really not user-controlled?".
88
-
89
- `SsrfProtectionConfig` (env-driven) configures *how* the guard behaves once it
90
- runs — the blocked/allowed IP ranges (`N8N_SSRF_BLOCKED_IP_RANGES`,
73
+ or SSRF). Because the secure default lives in the factory rather than at each
74
+ call site, forgetting to add protection cannot silently introduce a
75
+ vulnerability — the unsafe choice is the explicit one, and a new call site is
76
+ safe unless someone consciously writes `useDefaultSsrfPolicy: 'unsafe'`.
77
+
78
+ ### `enabled` is resolved inside `OutboundHttp`, not by callers
79
+
80
+ `useDefaultSsrfPolicy` answers one question: *is this destination trusted enough to skip
81
+ the guard?* Only the calling code knows that, so it is decided per call. Whether
82
+ the guard actually runs for a `'safe'` call is a separate, instance-wide
83
+ decision that `OutboundHttp` resolves internally from
84
+ `SsrfProtectionConfig.enabled` (`N8N_SSRF_PROTECTION_ENABLED`). Callers neither
85
+ read that flag nor inject the `SsrfProtectionService` — passing `'safe'` (or
86
+ nothing) is enough.
87
+
88
+ Concretely, a `'safe'` call is guarded when the instance enables protection and
89
+ passes through untouched when it does not; an `'enforced'` call is always
90
+ guarded and an `'unsafe'` call is never guarded, regardless of the flag.
91
+
92
+ `SsrfProtectionConfig` (env-driven) also configures *how* the guard behaves once
93
+ it runs — the blocked/allowed IP ranges (`N8N_SSRF_BLOCKED_IP_RANGES`,
91
94
  `N8N_SSRF_ALLOWED_IP_RANGES`), the allowed and blocked hostnames
92
95
  (`N8N_SSRF_ALLOWED_HOSTNAMES`, `N8N_SSRF_BLOCKED_HOSTNAMES`), and the DNS-cache
93
- size. Its `enabled` flag (`N8N_SSRF_PROTECTION_ENABLED`) is the **instance-wide gate that high-risk call sites consult** to decide whether to turn the guard on (see below).
96
+ size.
94
97
 
95
- The config sets the policy; the call site decides whether that policy applies to *this* destination.
98
+ ### Choosing a safety mode: when to opt out
96
99
 
97
- ### Choosing an SSRF level: low-risk vs high-risk calls
98
-
99
- Classify the **destination**, then pick:
100
+ Leave the default `'safe'` unless the destination genuinely cannot be
101
+ user-controlled. Classify the **destination**, then pick:
100
102
 
101
103
  | Destination | Risk | What to pass |
102
104
  | --- | --- | --- |
103
- | Fixed n8n-owned host, or a fixed public vendor API (Slack, Linear, npm registry default, AWS service endpoint) | **Low** — not user-controllable | `ssrf: 'disabled'` + a one-line "fixed host" comment |
104
- | Admin-configured infrastructure that may legitimately be internal (SAML/OIDC IdP, OTLP collector, log-streaming destination, external-secrets manager) | **Low–medium** — operator-trusted | `ssrf: 'disabled'` + a "may point at internal X" comment. The `N8N_SSRF_ALLOWED_*` allowlists are the escape hatch when the instance runs with protection globally on. |
105
- | User- or remote-controlled URL (workflow import URL, credential/OAuth URLs, a discovery document's second hop, a user-supplied registry, an LLM/web-research target) | **High** — attacker-influenceable | gate on the instance setting: `ssrf: ssrfConfig.enabled ? ssrfProtectionService : 'disabled'` |
105
+ | User- or remote-controlled URL (workflow import URL, credential/OAuth URLs, a discovery document's second hop, a user-supplied registry) | **High** — attacker-influenceable | nothing — the default `'safe'` guards it when the instance enables protection |
106
+ | URL an autonomous component picks on its own (an LLM/web-research target) | **High** — attacker-influenceable, and the operator never sees the URL | `useDefaultSsrfPolicy: 'enforced'` — guarded even when the instance leaves protection off |
107
+ | Fixed n8n-owned host, or a fixed public vendor API (Slack, Linear, npm registry default, AWS service endpoint) | **Low** — not user-controllable | `useDefaultSsrfPolicy: 'unsafe'` + a one-line "fixed host" comment |
108
+ | Admin-configured infrastructure that may legitimately be internal (SAML/OIDC IdP, OTLP collector, log-streaming destination, external-secrets manager) | **Low–medium** — operator-trusted | `useDefaultSsrfPolicy: 'unsafe'` + a "may point at internal X" comment. The `N8N_SSRF_ALLOWED_*` allowlists are the alternative when the instance runs with protection globally on. |
106
109
 
107
110
  ```ts
108
- // LOW risk — fixed, n8n-owned host.
109
- const client = this.outboundHttp.requests({ ssrf: 'disabled' });
111
+ // HIGH risk — `url` comes from user input. Guarded by default.
112
+ const client = this.outboundHttp.requests();
110
113
 
111
- // HIGH risk — `url` comes from user input. Guard when the instance enables it.
112
- const client = this.outboundHttp.requests({
113
- ssrf: this.ssrfConfig.enabled ? this.ssrfProtectionService : 'disabled',
114
- });
114
+ // LOW risk — fixed, n8n-owned host. Opt out explicitly.
115
+ const client = this.outboundHttp.requests({ useDefaultSsrfPolicy: 'unsafe' });
115
116
  ```
116
117
 
117
- Whatever you pick, **write a one-line comment stating why** — that comment is
118
- what a security reviewer reads. Reference implementations:
119
- `packages/cli/src/oauth/oauth.service.ts` (config-gated high-risk) and
118
+ Whenever you opt out, **write a one-line comment stating why** — that comment is
119
+ what a security reviewer reads. Reference implementation:
120
120
  `packages/nodes-base/credentials/common/token-request.ts`, which lifts the
121
121
  low/high-risk choice into the type system with a
122
122
  `'fixed-vendor' | 'user-controlled'` parameter so the caller is forced to
123
123
  classify the destination.
124
124
 
125
+ ### The node execution path
126
+
127
+ Node request helpers (`this.helpers.httpRequest`,
128
+ `httpRequestWithAuthentication`, and the deprecated `request`) go through
129
+ `OutboundHttp.requests()` with the default safe mode. `IHttpRequestOptions`
130
+ exposes no `useDefaultSsrfPolicy` field, so node code — community nodes included — cannot
131
+ opt out of the instance's policy.
132
+
133
+ The raw `httpRequest` function is not exported from the package barrel. It is
134
+ reachable only through the `@n8n/backend-network/testing` entry point, so
135
+ production code cannot reach an unguarded request function.
136
+
125
137
  ## The boundary rule
126
138
 
127
139
  The `n8n-local-rules/no-uncentralized-http` ESLint rule enforces this.