build: Move SSRF protection into @n8n/backend-network package (#32070)

Co-authored-by: Emilia <100027345+sovietspaceship@users.noreply.github.com>
This commit is contained in:
Lorent Lempereur
2026-06-11 16:23:51 +02:00
committed by GitHub
parent 6afba57091
commit 10f398dd56
46 changed files with 160 additions and 101 deletions
+12 -4
View File
@@ -12,8 +12,8 @@
"lint": "eslint . --quiet",
"lint:fix": "eslint . --fix",
"watch": "tsc -p tsconfig.build.json --watch",
"test": "vitest run --passWithNoTests",
"test:unit": "vitest run --passWithNoTests",
"test": "vitest run",
"test:unit": "vitest run",
"test:dev": "vitest --silent=false"
},
"main": "dist/index.js",
@@ -23,13 +23,21 @@
"dist/**/*"
],
"dependencies": {
"@n8n/backend-common": "workspace:*"
"@n8n/backend-common": "workspace:*",
"@n8n/config": "workspace:*",
"@n8n/constants": "workspace:*",
"@n8n/di": "workspace:*",
"cache-manager": "catalog:",
"n8n-workflow": "workspace:*",
"reflect-metadata": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@n8n/typescript-config": "workspace:*",
"@n8n/vitest-config": "workspace:*",
"@vitest/coverage-v8": "catalog:",
"vitest": "catalog:"
"vitest": "catalog:",
"vitest-mock-extended": "catalog:"
},
"license": "LicenseRef-n8n-sustainable-use"
}
@@ -0,0 +1,2 @@
export { DnsResolver } from './dns-resolver';
export { InMemoryDnsCache } from './in-memory-dns-cache.service';
+2 -1
View File
@@ -1 +1,2 @@
export {};
export * from './dns';
export * from './ssrf';
@@ -3,7 +3,7 @@ import { SsrfProtectionConfig } from '@n8n/config';
import type { LookupAddress } from 'node:dns';
import { mock } from 'vitest-mock-extended';
import type { DnsResolver } from '../dns-resolver';
import type { DnsResolver } from '../../dns';
import { SsrfBlockedIpError } from '../ssrf-blocked-ip.error';
import { SsrfProtectionService } from '../ssrf-protection.service';
@@ -317,8 +317,8 @@ describe('SsrfProtectionService', () => {
const [error, address, family] = await new Promise<
[Error | null, string, number | undefined]
>((resolve) =>
lookup('example.com', { all: false }, (err, addr, fam) =>
resolve([err, addr as string, fam]),
lookup('example.com', { all: false }, (lookupError, addr, fam) =>
resolve([lookupError, addr as string, fam]),
),
);
@@ -355,8 +355,8 @@ describe('SsrfProtectionService', () => {
const lookup = service.createSecureLookup();
const [error, addresses] = await new Promise<[Error | null, LookupAddress[]]>((resolve) =>
lookup('multi.example.com', { all: true }, (err, addrs) =>
resolve([err, addrs as LookupAddress[]]),
lookup('multi.example.com', { all: true }, (lookupError, addrs) =>
resolve([lookupError, addrs as LookupAddress[]]),
),
);
@@ -377,8 +377,8 @@ describe('SsrfProtectionService', () => {
const [error, address, family] = await new Promise<
[Error | null, string, number | undefined]
>((resolve) =>
lookup('dualstack.example.com', { all: false, family: 6 }, (err, addr, fam) =>
resolve([err, addr as string, fam]),
lookup('dualstack.example.com', { all: false, family: 6 }, (lookupError, addr, fam) =>
resolve([lookupError, addr as string, fam]),
),
);
@@ -401,8 +401,8 @@ describe('SsrfProtectionService', () => {
const [error, address, family] = await new Promise<
[Error | null, string, number | undefined]
>((resolve) =>
lookup('ipv4-only.example.com', { all: false, family: 6 }, (err, addr, fam) =>
resolve([err, addr as string, fam]),
lookup('ipv4-only.example.com', { all: false, family: 6 }, (lookupError, addr, fam) =>
resolve([lookupError, addr as string, fam]),
),
);
@@ -430,8 +430,8 @@ describe('SsrfProtectionService', () => {
const lookup = service.createSecureLookup();
const [error, address] = await new Promise<[Error | null, string]>((resolve) =>
lookup('api.internal.n8n.io', { all: false }, (err, addr) =>
resolve([err, addr as string]),
lookup('api.internal.n8n.io', { all: false }, (lookupError, addr) =>
resolve([lookupError, addr as string]),
),
);
@@ -449,8 +449,8 @@ describe('SsrfProtectionService', () => {
const [error, address, family] = await new Promise<
[Error | null, string, number | undefined]
>((resolve) =>
lookup('failing.example.com', { all: false }, (err, addr, fam) =>
resolve([err, addr as string, fam]),
lookup('failing.example.com', { all: false }, (lookupError, addr, fam) =>
resolve([lookupError, addr as string, fam]),
),
);
@@ -469,8 +469,8 @@ describe('SsrfProtectionService', () => {
const [error, addresses, family] = await new Promise<
[Error | null, LookupAddress[], number | undefined]
>((resolve) =>
lookup('failing.example.com', { all: true }, (err, addrs, fam) =>
resolve([err, addrs as LookupAddress[], fam]),
lookup('failing.example.com', { all: true }, (lookupError, addrs, fam) =>
resolve([lookupError, addrs as LookupAddress[], fam]),
),
);
@@ -0,0 +1,3 @@
export { SsrfProtectionService } from './ssrf-protection.service';
export type { SsrfBridge } from './ssrf-protection.service';
export { SsrfBlockedIpError } from './ssrf-blocked-ip.error';
@@ -7,7 +7,7 @@ import type { LookupAddress, LookupOptions } from 'node:dns';
import type { BlockList, LookupFunction } from 'node:net';
import { isIP } from 'node:net';
import { DnsResolver } from './dns-resolver';
import { DnsResolver } from '../dns';
import { HostnameMatcher } from './hostname-matcher';
import { buildIpRangeList } from './ip-range-builder';
import { SsrfBlockedIpError } from './ssrf-blocked-ip.error';
+7 -1
View File
@@ -7,5 +7,11 @@
"emitDecoratorMetadata": true
},
"include": ["src/**/*.ts"],
"references": [{ "path": "../backend-common/tsconfig.build.json" }]
"references": [
{ "path": "../../workflow/tsconfig.build.cjs.json" },
{ "path": "../backend-common/tsconfig.build.json" },
{ "path": "../config/tsconfig.build.json" },
{ "path": "../constants/tsconfig.build.json" },
{ "path": "../di/tsconfig.build.json" }
]
}
+1
View File
@@ -118,6 +118,7 @@
"@n8n/ai-workflow-builder": "workspace:*",
"@n8n/api-types": "workspace:*",
"@n8n/backend-common": "workspace:*",
"@n8n/backend-network": "workspace:*",
"@n8n/chat-hub": "workspace:*",
"@n8n/client-oauth2": "workspace:*",
"@n8n/config": "workspace:*",
@@ -1,6 +1,6 @@
import type { InMemoryDnsCache } from '@n8n/backend-network';
import { mockInstance } from '@n8n/backend-test-utils';
import { PrometheusMetricsConfig, SsrfProtectionConfig } from '@n8n/config';
import type { InMemoryDnsCache } from 'n8n-core';
import promClient from 'prom-client';
import { PrometheusDnsCacheMetricsService } from '../dns-cache-metrics.service';
@@ -1,6 +1,6 @@
import type { SsrfProtectionService } from '@n8n/backend-network';
import { mockInstance } from '@n8n/backend-test-utils';
import { PrometheusMetricsConfig, SsrfProtectionConfig } from '@n8n/config';
import type { SsrfProtectionService } from 'n8n-core';
import promClient from 'prom-client';
import { PrometheusSsrfMetricsService } from '../ssrf-metrics.service';
@@ -1,6 +1,6 @@
import { InMemoryDnsCache } from '@n8n/backend-network';
import { PrometheusMetricsConfig, SsrfProtectionConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import { InMemoryDnsCache } from 'n8n-core';
import promClient from 'prom-client';
import type { PrometheusMetricsCollector } from './base';
@@ -1,10 +1,10 @@
import { PrometheusMetricsConfig, SsrfProtectionConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import { SsrfProtectionService } from 'n8n-core';
import promClient from 'prom-client';
import type { PrometheusMetricsCollector } from './base';
import { DURATION_BUCKETS_SECONDS } from './constant';
import { SsrfProtectionService } from '@n8n/backend-network';
/**
* Tracks SSRF check results as counters and duration as a histogram.
@@ -56,7 +56,7 @@ import type { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee'
import type { ExecutionPersistence } from '@/executions/execution-persistence';
import type { EventService } from '@/events/event.service';
import type { RoleService } from '@/services/role.service';
import type { SsrfProtectionService } from 'n8n-core';
import type { SsrfProtectionService } from '@n8n/backend-network';
import type { Telemetry } from '@/telemetry';
jest.mock('@/permissions.ee/check-access');
@@ -63,6 +63,7 @@ import {
WorkflowRepository,
} from '@n8n/db';
import { Logger } from '@n8n/backend-common';
import { SsrfProtectionService } from '@n8n/backend-network';
import { Container, Service } from '@n8n/di';
import { hasGlobalScope, PROJECT_OWNER_ROLE_SLUG, type Scope } from '@n8n/permissions';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
@@ -117,7 +118,7 @@ import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters
import { FolderService } from '@/services/folder.service';
import { ProjectService } from '@/services/project.service.ee';
import { RoleService } from '@/services/role.service';
import { InstanceSettings, SsrfProtectionService } from 'n8n-core';
import { InstanceSettings } from 'n8n-core';
import { TagService } from '@/services/tag.service';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';
@@ -87,7 +87,7 @@ import {
ThreadTaskStorage,
} from '@n8n/instance-ai';
import { setSchemaBaseDirs } from '@n8n/workflow-sdk';
import { ErrorReporter, InstanceSettings, SsrfProtectionService } from 'n8n-core';
import { ErrorReporter, InstanceSettings } from 'n8n-core';
import { OperationalError, UnexpectedError, UserError } from 'n8n-workflow';
import { nanoid } from 'nanoid';
import type * as Undici from 'undici';
@@ -139,6 +139,7 @@ import { AiService } from '@/services/ai.service';
import { ProxyTokenManager } from '@/services/proxy-token-manager';
import { UrlService } from '@/services/url.service';
import { Telemetry } from '@/telemetry';
import { SsrfProtectionService } from '@n8n/backend-network';
function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
@@ -1,13 +1,11 @@
import type { Logger } from '@n8n/backend-common';
import type { DnsResolver, SsrfBridge } from '@n8n/backend-network';
import { SsrfProtectionService } from '@n8n/backend-network';
import { SsrfProtectionConfig } from '@n8n/config';
import { mock } from 'jest-mock-extended';
import type { SsrfBridge } from 'n8n-core';
import { createResultOk } from 'n8n-workflow';
import type { LookupFunction } from 'node:net';
import type { DnsResolver } from 'n8n-core';
import { SsrfProtectionService } from 'n8n-core';
import { fetchAndExtract } from '../fetch-and-extract';
function createSsrfMock(): jest.Mocked<SsrfBridge> {
@@ -1,11 +1,11 @@
import type * as JoplinTurndownGfm from '@joplin/turndown-plugin-gfm';
import type { Readability as TReadability } from '@mozilla/readability';
import type * as ReadabilityMod from '@mozilla/readability';
import type { SsrfBridge } from '@n8n/backend-network';
import type { FetchedPage } from '@n8n/instance-ai';
import type * as LinkedomMod from 'linkedom';
import type { parseHTML as TParseHtml } from 'linkedom';
import type { SsrfBridge } from 'n8n-core';
import type TTurndownService from 'turndown';
import type * as ReadabilityMod from '@mozilla/readability';
import type * as TurndownMod from 'turndown';
import { Agent } from 'undici';
@@ -14,7 +14,7 @@ import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import type { Push } from '@/push';
import { WorkflowBuilderService } from '@/services/ai-workflow-builder.service';
import type { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service';
import type { SsrfProtectionService } from 'n8n-core';
import type { SsrfProtectionService } from '@n8n/backend-network';
import type { UrlService } from '@/services/url.service';
import type { Telemetry } from '@/telemetry';
import type { WorkflowBuilderSessionRepository } from '@/modules/workflow-builder';
@@ -2,6 +2,7 @@ import { AiWorkflowBuilderService, createPassthroughSsrfGuard } from '@n8n/ai-wo
import type { ResourceLocatorCallbackFactory } from '@n8n/ai-workflow-builder';
import { ChatPayload } from '@n8n/ai-workflow-builder/dist/workflow-builder-agent';
import { Logger } from '@n8n/backend-common';
import { SsrfProtectionService } from '@n8n/backend-network';
import { GlobalConfig, SsrfProtectionConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import { AiAssistantClient } from '@n8n_io/ai-assistant-sdk';
@@ -21,7 +22,7 @@ import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { WorkflowBuilderSessionRepository } from '@/modules/workflow-builder';
import { Push } from '@/push';
import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service';
import { InstanceSettings, SsrfProtectionService } from 'n8n-core';
import { InstanceSettings } from 'n8n-core';
import { UrlService } from '@/services/url.service';
import { Telemetry } from '@/telemetry';
import { getBase } from '@/workflow-execute-additional-data';
@@ -2,44 +2,45 @@
import type { PushMessage, PushType } from '@n8n/api-types';
import { Logger, ModuleRegistry } from '@n8n/backend-common';
import { SsrfProtectionService } from '@n8n/backend-network';
import { ExecutionsConfig, GlobalConfig, SsrfProtectionConfig } from '@n8n/config';
import { Time } from '@n8n/constants';
import { ExecutionRepository, WorkflowRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import type { ServiceIdentifier } from '@n8n/di';
import { Container } from '@n8n/di';
import type { JSONSchema7 } from 'json-schema';
import { ExternalSecretsProxy, SsrfProtectionService, WorkflowExecute } from 'n8n-core';
import { ExternalSecretsProxy, WorkflowExecute } from 'n8n-core';
import type {
AiEvent,
EnvProviderState,
ExecuteAgentData,
ExecuteWorkflowData,
ExecuteWorkflowOptions,
ExecutionError,
ExecutionStatus,
IDataObject,
IExecuteData,
IExecuteFunctions,
IExecuteWorkflowInfo,
INode,
INodeExecutionData,
INodeParameters,
IRun,
IRunExecutionData,
ITaskDataConnections,
IWorkflowBase,
IWorkflowExecuteAdditionalData,
IWorkflowExecutionDataProcess,
IWorkflowSettings,
RelatedExecution,
WorkflowExecuteMode,
} from 'n8n-workflow';
import {
UnexpectedError,
Workflow,
createRunExecutionData,
mergeRunsPerBranch,
} from 'n8n-workflow';
import type {
AiEvent,
IDataObject,
IExecuteData,
IExecuteWorkflowInfo,
INode,
INodeExecutionData,
INodeParameters,
IWorkflowBase,
IWorkflowExecuteAdditionalData,
IWorkflowSettings,
WorkflowExecuteMode,
ExecutionStatus,
ExecutionError,
IExecuteFunctions,
ITaskDataConnections,
ExecuteWorkflowOptions,
IWorkflowExecutionDataProcess,
EnvProviderState,
ExecuteWorkflowData,
ExecuteAgentData,
RelatedExecution,
IRun,
IRunExecutionData,
} from 'n8n-workflow';
import { ActiveExecutions } from '@/active-executions';
import { CredentialsHelper } from '@/credentials-helper';
@@ -1,5 +1,7 @@
import type { ImportWorkflowFromUrlDto } from '@n8n/api-types';
import type { Logger } from '@n8n/backend-common';
import type { SsrfProtectionService } from '@n8n/backend-network';
import { SsrfBlockedIpError } from '@n8n/backend-network';
import type { SsrfProtectionConfig } from '@n8n/config';
import type { AuthenticatedRequest, IExecutionResponse } from '@n8n/db';
import axios from 'axios';
@@ -13,8 +15,6 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import type { ExecutionService } from '@/executions/execution.service';
import type { ProjectService } from '@/services/project.service.ee';
import { SsrfBlockedIpError } from 'n8n-core';
import type { SsrfProtectionService } from 'n8n-core';
jest.mock('axios');
@@ -60,9 +60,9 @@ import { AuthService } from '@/auth/auth.service';
import * as ResponseHelper from '@/response-helper';
import { NamingService } from '@/services/naming.service';
import { ProjectService } from '@/services/project.service.ee';
import { SsrfBlockedIpError, SsrfProtectionService } from 'n8n-core';
import { UserManagementMailer } from '@/user-management/email';
import * as utils from '@/utils';
import { SsrfBlockedIpError, SsrfProtectionService } from '@n8n/backend-network';
@RestController('/workflows')
export class WorkflowsController {
+1
View File
@@ -55,6 +55,7 @@
"@aws-sdk/client-s3": "3.808.0",
"@langchain/core": "catalog:",
"@n8n/backend-common": "workspace:*",
"@n8n/backend-network": "workspace:*",
"@n8n/client-oauth2": "workspace:*",
"@n8n/config": "workspace:*",
"@n8n/constants": "workspace:*",
+14 -15
View File
@@ -1,3 +1,4 @@
import type { SsrfBridge } from '@n8n/backend-network';
import type {
DataTableProxyProvider,
DynamicCredentialCheckProxyProvider,
@@ -8,8 +9,6 @@ import type {
OauthJweProxyProvider,
} from 'n8n-workflow';
import type { SsrfBridge } from '@/ssrf';
import type { ExecutionLifecycleHooks } from './execution-lifecycle-hooks';
import type { ExternalSecretsProxy } from './external-secrets-proxy';
@@ -85,24 +84,24 @@ declare module 'n8n-workflow' {
}
export * from './active-workflow-triggers';
export * from './scheduled-task-manager';
export type * from './interfaces';
export * from './routing-node';
export * from './node-execution-context';
export * from './partial-execution-utils';
export * from './node-execution-context/utils/execution-metadata';
export * from './workflow-execute';
export * from './execution-context-hook-registry.service';
export { ExecutionLifecycleHooks } from './execution-lifecycle-hooks';
export { ExternalSecretsProxy, type IExternalSecretsManager } from './external-secrets-proxy';
export { ExecutionContextService } from './execution-context.service';
export { establishExecutionContext } from './execution-context';
export { isEngineRequest } from './requests-response';
export {
synthesizeBinaryFixture,
type FixtureSizeHint,
type SynthesizeBinaryFixtureOptions,
} from './eval-mock-fixtures';
export { establishExecutionContext } from './execution-context';
export * from './execution-context-hook-registry.service';
export { ExecutionContextService } from './execution-context.service';
export { ExecutionLifecycleHooks } from './execution-lifecycle-hooks';
export { ExternalSecretsProxy, type IExternalSecretsManager } from './external-secrets-proxy';
export type * from './interfaces';
export * from './node-execution-context';
export * from './node-execution-context/utils/execution-metadata';
export * from './partial-execution-utils';
export { isEngineRequest } from './requests-response';
export * from './routing-node';
export * from './scheduled-task-manager';
export * from './workflow-execute';
// Exposed so eval-mode credential helpers (e.g. `EvalMockedCredentialsHelper`)
// can reuse the same schema-driven cred synthesizer the wire-server URL
// rewrite expects. See its `getDecrypted` catch path for the consumer.
@@ -1,4 +1,6 @@
import type { Logger } from '@n8n/backend-common';
import type { DnsResolver, SsrfBridge } from '@n8n/backend-network';
import { SsrfProtectionService } from '@n8n/backend-network';
import { SsrfProtectionConfig } from '@n8n/config';
import type {
IHttpRequestOptions,
@@ -13,8 +15,6 @@ import type { MockProxy } from 'vitest-mock-extended';
import { mock } from 'vitest-mock-extended';
import type { ExecutionLifecycleHooks } from '@/execution-engine/execution-lifecycle-hooks';
import type { DnsResolver, SsrfBridge } from '@/ssrf';
import { SsrfProtectionService } from '@/ssrf';
import { getRequestHelperFunctions } from '../request-helper-functions';
import { httpRequest } from '../request-helpers/http-request';
@@ -1,3 +1,4 @@
import type { SsrfBridge } from '@n8n/backend-network';
import { AiConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import FormData from 'form-data';
@@ -6,8 +7,6 @@ import type { IHttpRequestMethods, IHttpRequestOptions, IRequestOptions } from '
import nock from 'nock';
import { mock } from 'vitest-mock-extended';
import type { SsrfBridge } from '@/ssrf';
// Imported for side effects: sets axios defaults and registers the vendor-header interceptor
import '../axios-config';
import {
@@ -1,3 +1,4 @@
import type { SsrfBridge } from '@n8n/backend-network';
import FormData from 'form-data';
import type { Agent as HttpsAgent } from 'https';
import type {
@@ -12,7 +13,6 @@ import type { SecureContextOptions } from 'tls';
import { mock } from 'vitest-mock-extended';
import type { ExecutionLifecycleHooks } from '@/execution-engine/execution-lifecycle-hooks';
import type { SsrfBridge } from '@/ssrf';
import { parseRequestObject, proxyRequestToAxios } from '../legacy-request-adapter';
@@ -1,4 +1,5 @@
import { Logger } from '@n8n/backend-common';
import type { SsrfBridge } from '@n8n/backend-network';
import { Container } from '@n8n/di';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
@@ -13,7 +14,6 @@ import {
} from 'n8n-workflow';
import { createHttpProxyAgent, createHttpsProxyAgent } from '@/http-proxy';
import type { SsrfBridge } from '@/ssrf';
export function throwIfDomainNotAllowed(
configOrUrl: AxiosRequestConfig | string,
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import type { SsrfBridge } from '@n8n/backend-network';
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import type { AgentOptions } from 'https';
@@ -13,8 +14,6 @@ import type {
import { isObjectEmpty } from 'n8n-workflow';
import { stringify } from 'qs';
import type { SsrfBridge } from '@/ssrf';
import {
buildTargetUrl,
digestAuthAxiosConfig,
@@ -7,6 +7,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { Logger } from '@n8n/backend-common';
import type { SsrfBridge } from '@n8n/backend-network';
import { Container } from '@n8n/di';
import type { AxiosHeaders, AxiosRequestConfig } from 'axios';
import crypto from 'crypto';
@@ -25,8 +26,6 @@ import { NodeSslError } from 'n8n-workflow';
import { stringify } from 'qs';
import { Readable } from 'stream';
import type { SsrfBridge } from '@/ssrf';
import { binaryToString } from '../binary-helper-functions';
import { parseIncomingMessage } from '../parse-incoming-message';
import {
-1
View File
@@ -8,7 +8,6 @@ export * from './data-deduplication-service';
export * from './encryption';
export * from './errors';
export * from './execution-engine';
export * from './ssrf';
export * from './html-sandbox';
export * from './instance-settings';
export * from './nodes-loader';
-7
View File
@@ -1,7 +0,0 @@
export { DnsResolver } from './dns-resolver';
export type { DnsLookupOptions } from './dns-resolver';
export { InMemoryDnsCache } from './in-memory-dns-cache.service';
export type { DnsCacheEventMap } from './in-memory-dns-cache.service';
export { SsrfBlockedIpError } from './ssrf-blocked-ip.error';
export { SsrfProtectionService } from './ssrf-protection.service';
export type { SsrfBridge, SsrfCheckResult, SsrfEventMap } from './ssrf-protection.service';
+1
View File
@@ -20,6 +20,7 @@
{ "path": "../workflow/tsconfig.build.esm.json" },
{ "path": "../@n8n/decorators/tsconfig.build.json" },
{ "path": "../@n8n/backend-common/tsconfig.build.json" },
{ "path": "../@n8n/backend-network/tsconfig.build.json" },
{ "path": "../@n8n/config/tsconfig.build.json" },
{ "path": "../@n8n/constants/tsconfig.build.json" },
{ "path": "../@n8n/di/tsconfig.build.json" },
+11
View File
@@ -23,6 +23,17 @@ export default mergeConfig(
find: /^zod$/,
replacement: require.resolve('zod'),
},
// `n8n-workflow` has dual ESM/CJS builds (`./dist/esm/index.js` for `import`,
// `./dist/cjs/index.js` for `require`), each with its own `UserError` class.
// `@n8n/backend-network` is loaded from its CJS dist and `require`s the CJS copy,
// so `SsrfBlockedIpError extends UserError` (CJS), while test files ESM-import
// `UserError` (ESM) — and `instanceof` fails between them. Pin the top-level
// `n8n-workflow` import to the CJS file so both code paths share one module
// instance. `require.resolve` follows the `require` export condition.
{
find: /^n8n-workflow$/,
replacement: require.resolve('n8n-workflow'),
},
],
},
oxc: {
+1
View File
@@ -869,6 +869,7 @@
]
},
"devDependencies": {
"@n8n/backend-network": "workspace:*",
"@n8n/client-oauth2": "workspace:*",
"@n8n/eslint-plugin-community-nodes": "workspace:*",
"@n8n/playwright-janitor": "workspace:*",
@@ -4,8 +4,9 @@ import { mock } from 'jest-mock-extended';
import get from 'lodash/get';
import merge from 'lodash/merge';
import set from 'lodash/set';
import type { SsrfBridge } from '@n8n/backend-network';
import { PollContext, returnJsonArray } from 'n8n-core';
import type { InstanceSettings, ExecutionLifecycleHooks, SsrfBridge } from 'n8n-core';
import type { InstanceSettings, ExecutionLifecycleHooks } from 'n8n-core';
import { ScheduledTaskManager } from 'n8n-core/dist/execution-engine/scheduled-task-manager';
import {
createDeferredPromise,
+33
View File
@@ -1141,6 +1141,27 @@ importers:
'@n8n/backend-common':
specifier: workspace:*
version: link:../backend-common
'@n8n/config':
specifier: workspace:*
version: link:../config
'@n8n/constants':
specifier: workspace:*
version: link:../constants
'@n8n/di':
specifier: workspace:*
version: link:../di
cache-manager:
specifier: 'catalog:'
version: 5.2.3
n8n-workflow:
specifier: workspace:*
version: link:../../workflow
reflect-metadata:
specifier: 'catalog:'
version: 0.2.2
zod:
specifier: 3.25.67
version: 3.25.67
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
@@ -1154,6 +1175,9 @@ importers:
vitest:
specifier: 'catalog:'
version: 4.1.1(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.0.16)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.41)(esbuild@0.25.10)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))
vitest-mock-extended:
specifier: 'catalog:'
version: 3.1.0(typescript@6.0.2)(vitest@4.1.1(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@types/node@20.19.41)(esbuild@0.25.10)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)))
packages/@n8n/backend-test-utils:
dependencies:
@@ -3077,6 +3101,9 @@ importers:
'@n8n/backend-common':
specifier: workspace:*
version: link:../@n8n/backend-common
'@n8n/backend-network':
specifier: workspace:*
version: link:../@n8n/backend-network
'@n8n/chat-hub':
specifier: workspace:*
version: link:../@n8n/chat-hub
@@ -3549,6 +3576,9 @@ importers:
'@n8n/backend-common':
specifier: workspace:*
version: link:../@n8n/backend-common
'@n8n/backend-network':
specifier: workspace:*
version: link:../@n8n/backend-network
'@n8n/client-oauth2':
specifier: workspace:*
version: link:../@n8n/client-oauth2
@@ -4985,6 +5015,9 @@ importers:
specifier: 3.1.0
version: 3.1.0
devDependencies:
'@n8n/backend-network':
specifier: workspace:*
version: link:../@n8n/backend-network
'@n8n/client-oauth2':
specifier: workspace:*
version: link:../@n8n/client-oauth2