n8n 2.40.5 - Base URL Ownership Check on the 2.40 Line


n8n published [email protected] on 21 September 2026 at 14:03 UTC. The tag is a stable patch. The only change limits declarative routing while the engine checks whether a baseURL belongs to the node.

The full release notes and downloads are on the GitHub release page. The compare view from [email protected] to this tag is one commit, ebe5a73, tracked as #39147.

The commit subject names the destination branch release-candidate/2.40.x. GitHub still publishes the tag as a normal release. The release metadata does not mark it as a prerelease. Installs pinned to [email protected] can take the guard without leaving the 2.40 line.

The patch touches two files and nothing else. packages/core/src/execution-engine/routing-node.ts is the behavior change. packages/core/src/execution-engine/__tests__/routing-node.test.ts adds the regression case. The diff is 25 lines added and 3 lines removed. No environment variable appears. No credential field appears. The cited pull request marks documentation as not applicable.

RoutingNode is the only class in the diff. resolveBaseUrl still replays requestDefaults.baseURL and still walks every property. The new argument is baseUrlOnly. That replay is the only caller that passes true into getRequestOptionsFromParameters.

runNode builds the live HTTP options before it asks who owns the host. The live call to getRequestOptionsFromParameters leaves baseUrlOnly off, so the default false applies. Path, query, headers, body, routing.operations, routing.send, and routing.output still evaluate with the real parameter map, credentials, and $version.

The ownership check comes second. runNode compares toHostname of the already resolved options.baseURL with toHostname of a fresh result from resolveBaseUrl. The second call passes $parameter: {} and $rawParameter: {}. It keeps $credentials and $version. Equal hosts set baseUrlIsNodeOwned. That boolean is credentialOwnedSurface for getCredentialAllowedDomains. A returned list is written to options.allowedDomains. The comment next to the compare says only the host matters, because that is all the domain allowlist reads.

On [email protected] the replay used the full walker. With the parameter objects empty, expressions for routing.operations, every routing.request key, routing.send, and routing.output still ran. A path that reads $parameter["endpoint"] has no value on that pass. Preparation could throw before the host compare finished, and the HTTP call never started. The regression comment names this NODE-6014.

The flag stops that extra work. routing.operations is copied only when baseUrlOnly is false. The routing.request loop skips every key other than baseURL, so url, headers, query, and body expressions on that object do not run during the cleared pass. routing.send is skipped, so preSend is not collected and paginate is not evaluated. routing.output is skipped, so maxResults and postReceive stay out, including an enabled expression on a postReceive action.

The walker still descends. displayParameter still decides whether a property is visible. Selected options, collection entries, and fixedCollection items are still visited. Three recursive calls forward baseUrlOnly. A property level baseURL override still merges into the scratch object. resolveBaseUrl returns only scratch.options.baseURL. The method comment names two shapes that must keep working: a parameter wired into requestDefaults.baseURL, and a property that overrides baseURL in its own routing. Google Cloud Storage upload operations are the example in that comment.

There is a real narrowing. A host that lives only on url, a header, a query field, or a preSend hook will not flip credentialOwnedSurface on this pass. Put caller supplied hosts on baseURL.

The new test name is prepares a dynamic path segment when the base URL is static. It sits in routing-node.test.ts and cites NODE-6014. runWithCredential receives { apiKey: 'testApiKey' }. Parameters set endpoint to project-123. routedUrl is =/tests/{{toPathSegment($parameter["endpoint"])}}. The test reads requestOptions.url and expects /tests/project-123.

Parameters exist on that path. The assertion locks the default of baseUrlOnly. The cleared replay may ignore toPathSegment. The live request must still place project-123 in the path. Setting the flag inside runNode would fail the test. Calls that already succeeded on [email protected] keep the same URL, because that build happens with the flag off, before resolveBaseUrl runs.

#39147 lists the checks used for the backport: pnpm test src/execution-engine/__tests__/routing-node.test.ts from packages/core, then pnpm lint and pnpm typecheck in the same package.

Take [email protected] when a declarative HTTP node fails while a static baseURL sits next to a path that reads a parameter. Also take it if you want the ownership replay to stop running send and output expressions against empty parameters. Stay on [email protected] only when that failure is absent and you accept the wider replay.

No migration is described. Workflow JSON does not need a rewrite. Credential records do not change. The allowlist can shrink if a node inferred its host from something other than baseURL. When a domain check starts rejecting a host that used to pass, move that host onto baseURL in requestDefaults or in the property routing block.

This tag contains no other fix. Confirm the single bullet on the GitHub release page.