@n8n/backend-network 1.0.0 → 1.1.1
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/LICENSE_EE.md +27 -0
- package/dist/build.tsbuildinfo +1 -1
- package/dist/dns/dns-resolver.d.ts +13 -0
- package/dist/dns/dns-resolver.js +93 -0
- package/dist/dns/dns-resolver.js.map +1 -0
- package/dist/dns/in-memory-dns-cache.service.d.ts +21 -0
- package/dist/dns/in-memory-dns-cache.service.js +73 -0
- package/dist/dns/in-memory-dns-cache.service.js.map +1 -0
- package/dist/dns/index.d.ts +2 -0
- package/dist/dns/index.js +8 -0
- package/dist/dns/index.js.map +1 -0
- package/dist/http/axios-config.d.ts +1 -0
- package/dist/http/axios-config.js +42 -0
- package/dist/http/axios-config.js.map +1 -0
- package/dist/http/axios-utils.d.ts +24 -0
- package/dist/http/axios-utils.js +257 -0
- package/dist/http/axios-utils.js.map +1 -0
- package/dist/http/binary-buffer.d.ts +3 -0
- package/dist/http/binary-buffer.js +24 -0
- package/dist/http/binary-buffer.js.map +1 -0
- package/dist/http/binary-string.d.ts +2 -0
- package/dist/http/binary-string.js +16 -0
- package/dist/http/binary-string.js.map +1 -0
- package/dist/http/http-proxy.d.ts +7 -0
- package/dist/http/http-proxy.js +113 -0
- package/dist/http/http-proxy.js.map +1 -0
- package/dist/http/http-request.d.ts +7 -0
- package/dist/http/http-request.js +157 -0
- package/dist/http/http-request.js.map +1 -0
- package/dist/http/index.d.ts +8 -0
- package/dist/http/index.js +39 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/outbound-user-agent.d.ts +4 -0
- package/dist/http/outbound-user-agent.js +40 -0
- package/dist/http/outbound-user-agent.js.map +1 -0
- package/dist/http/parse-incoming-message.d.ts +16 -0
- package/dist/http/parse-incoming-message.js +75 -0
- package/dist/http/parse-incoming-message.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/ssrf/hostname-matcher.d.ts +6 -0
- package/dist/ssrf/hostname-matcher.js +38 -0
- package/dist/ssrf/hostname-matcher.js.map +1 -0
- package/dist/ssrf/index.d.ts +3 -0
- package/dist/ssrf/index.js +8 -0
- package/dist/ssrf/index.js.map +1 -0
- package/dist/ssrf/ip-range-builder.d.ts +10 -0
- package/dist/ssrf/ip-range-builder.js +52 -0
- package/dist/ssrf/ip-range-builder.js.map +1 -0
- package/dist/ssrf/ssrf-blocked-ip.error.d.ts +6 -0
- package/dist/ssrf/ssrf-blocked-ip.error.js +21 -0
- package/dist/ssrf/ssrf-blocked-ip.error.js.map +1 -0
- package/dist/ssrf/ssrf-protection.service.d.ts +48 -0
- package/dist/ssrf/ssrf-protection.service.js +192 -0
- package/dist/ssrf/ssrf-protection.service.js.map +1 -0
- package/package.json +36 -8
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildIpRangeList = buildIpRangeList;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const node_net_1 = require("node:net");
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
function buildIpRangeList(cidrRanges) {
|
|
8
|
+
const list = new node_net_1.BlockList();
|
|
9
|
+
const issues = [];
|
|
10
|
+
for (const rawEntry of cidrRanges) {
|
|
11
|
+
const entry = rawEntry.trim();
|
|
12
|
+
if (entry.length === 0) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const cidr = parseCidr(entry);
|
|
17
|
+
if (!cidr) {
|
|
18
|
+
issues.push({ entry, error: 'Invalid CIDR notation' });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
list.addSubnet(cidr.network, cidr.prefix, cidr.family);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
const error = (0, n8n_workflow_1.ensureError)(e);
|
|
25
|
+
issues.push({ entry, error: `Failed to add IP range: ${error.message}` });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { list, issues };
|
|
29
|
+
}
|
|
30
|
+
function parseCidr(entry) {
|
|
31
|
+
const result = cidrSchema.safeParse(entry);
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const [network, prefixStr] = result.data.split('/');
|
|
36
|
+
const family = validateIp(network);
|
|
37
|
+
if (family === null) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const prefix = Number.parseInt(prefixStr, 10);
|
|
41
|
+
return { network, prefix, family };
|
|
42
|
+
}
|
|
43
|
+
const cidrSchema = zod_1.z.string().cidr();
|
|
44
|
+
function validateIp(ip) {
|
|
45
|
+
const version = (0, node_net_1.isIP)(ip);
|
|
46
|
+
if (version === 4)
|
|
47
|
+
return 'ipv4';
|
|
48
|
+
if (version === 6)
|
|
49
|
+
return 'ipv6';
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=ip-range-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-range-builder.js","sourceRoot":"","sources":["../../src/ssrf/ip-range-builder.ts"],"names":[],"mappings":";;AAmBA,4CAyBC;AA5CD,+CAA2C;AAC3C,uCAA2C;AAC3C,6BAAwB;AAiBxB,SAAgB,gBAAgB,CAAC,UAA6B;IAC7D,MAAM,IAAI,GAAG,IAAI,oBAAS,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,SAAS;QACV,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;gBACvD,SAAS;YACV,CAAC;YAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAA,0BAAW,EAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACzB,CAAC;AAKD,SAAS,SAAS,CACjB,KAAa;IAEb,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;AAErC,SAAS,UAAU,CAAC,EAAU;IAC7B,MAAM,OAAO,GAAG,IAAA,eAAI,EAAC,EAAE,CAAC,CAAC;IACzB,IAAI,OAAO,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,OAAO,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IACjC,OAAO,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SsrfBlockedIpError = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
class SsrfBlockedIpError extends n8n_workflow_1.UserError {
|
|
6
|
+
constructor(ip, hostname) {
|
|
7
|
+
const target = hostname ? `'${hostname}' (${ip})` : ip;
|
|
8
|
+
super('The request was blocked because it resolves to a restricted IP address', {
|
|
9
|
+
description: `The target ${target} is not allowed. ` +
|
|
10
|
+
'This is a security measure to prevent Server-Side Request Forgery (SSRF). ' +
|
|
11
|
+
'If you need to access internal resources, ask your n8n administrator to allowlist ' +
|
|
12
|
+
'the hostname or IP range in the environment configuration.',
|
|
13
|
+
extra: { ip, hostname },
|
|
14
|
+
});
|
|
15
|
+
this.name = 'SsrfBlockedIpError';
|
|
16
|
+
this.ip = ip;
|
|
17
|
+
this.hostname = hostname;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.SsrfBlockedIpError = SsrfBlockedIpError;
|
|
21
|
+
//# sourceMappingURL=ssrf-blocked-ip.error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf-blocked-ip.error.js","sourceRoot":"","sources":["../../src/ssrf/ssrf-blocked-ip.error.ts"],"names":[],"mappings":";;;AAAA,+CAAyC;AAKzC,MAAa,kBAAmB,SAAQ,wBAAS;IAIhD,YAAY,EAAU,EAAE,QAAiB;QACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,KAAK,CAAC,wEAAwE,EAAE;YAC/E,WAAW,EACV,cAAc,MAAM,mBAAmB;gBACvC,4EAA4E;gBAC5E,oFAAoF;gBACpF,4DAA4D;YAC7D,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;SACvB,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAnBD,gDAmBC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Logger, TypedEmitter } from '@n8n/backend-common';
|
|
2
|
+
import { SsrfProtectionConfig } from '@n8n/config';
|
|
3
|
+
import { Result } from 'n8n-workflow';
|
|
4
|
+
import type { LookupFunction } from 'node:net';
|
|
5
|
+
import { DnsResolver } from '../dns';
|
|
6
|
+
export type SsrfCheckResult = Result<void, Error>;
|
|
7
|
+
type SsrfBlockedPayload = {
|
|
8
|
+
phase: SsrfPhase;
|
|
9
|
+
reason: string;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
};
|
|
12
|
+
type SsrfAllowedPayload = {
|
|
13
|
+
phase: SsrfPhase;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
};
|
|
16
|
+
type SsrfPhase = 'pre_flight' | 'connect_time' | 'redirect';
|
|
17
|
+
export type SsrfEventMap = {
|
|
18
|
+
'ssrf.blocked': SsrfBlockedPayload;
|
|
19
|
+
'ssrf.allowed': SsrfAllowedPayload;
|
|
20
|
+
};
|
|
21
|
+
export interface SsrfBridge {
|
|
22
|
+
validateIp(ip: string): SsrfCheckResult;
|
|
23
|
+
validateUrl(url: string | URL): Promise<SsrfCheckResult>;
|
|
24
|
+
validateRedirectSync(url: string): void;
|
|
25
|
+
createSecureLookup(): LookupFunction;
|
|
26
|
+
}
|
|
27
|
+
export declare class SsrfProtectionService implements SsrfBridge {
|
|
28
|
+
private readonly ssrfConfig;
|
|
29
|
+
private readonly dnsResolver;
|
|
30
|
+
readonly events: TypedEmitter<SsrfEventMap>;
|
|
31
|
+
private readonly logger;
|
|
32
|
+
private readonly blockedIps;
|
|
33
|
+
private readonly allowedIps;
|
|
34
|
+
private readonly allowedHostnameMatcher;
|
|
35
|
+
constructor(ssrfConfig: SsrfProtectionConfig, dnsResolver: DnsResolver, logger: Logger);
|
|
36
|
+
validateUrl(url: string | URL): Promise<SsrfCheckResult>;
|
|
37
|
+
validateIp(ip: string): SsrfCheckResult;
|
|
38
|
+
createSecureLookup(): LookupFunction;
|
|
39
|
+
validateRedirectSync(url: string): void;
|
|
40
|
+
private normalizeIpInHostname;
|
|
41
|
+
private secureLookupAsync;
|
|
42
|
+
private lookupAndValidate;
|
|
43
|
+
private withEvents;
|
|
44
|
+
private toReason;
|
|
45
|
+
private tryParseUrl;
|
|
46
|
+
private getIpFamily;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SsrfProtectionService = void 0;
|
|
16
|
+
const backend_common_1 = require("@n8n/backend-common");
|
|
17
|
+
const config_1 = require("@n8n/config");
|
|
18
|
+
const di_1 = require("@n8n/di");
|
|
19
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
20
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
21
|
+
const node_net_1 = require("node:net");
|
|
22
|
+
const dns_1 = require("../dns");
|
|
23
|
+
const hostname_matcher_1 = require("./hostname-matcher");
|
|
24
|
+
const ip_range_builder_1 = require("./ip-range-builder");
|
|
25
|
+
const ssrf_blocked_ip_error_1 = require("./ssrf-blocked-ip.error");
|
|
26
|
+
let SsrfProtectionService = class SsrfProtectionService {
|
|
27
|
+
constructor(ssrfConfig, dnsResolver, logger) {
|
|
28
|
+
this.ssrfConfig = ssrfConfig;
|
|
29
|
+
this.dnsResolver = dnsResolver;
|
|
30
|
+
this.events = new backend_common_1.TypedEmitter();
|
|
31
|
+
this.logger = logger.scoped('ssrf-protection');
|
|
32
|
+
const blocked = (0, ip_range_builder_1.buildIpRangeList)(this.ssrfConfig.blockedIpRanges);
|
|
33
|
+
for (const issue of blocked.issues) {
|
|
34
|
+
this.logger.warn(`Invalid value '${issue.entry}' in N8N_SSRF_BLOCKED_IP_RANGES: ${issue.error}`);
|
|
35
|
+
}
|
|
36
|
+
this.blockedIps = blocked.list;
|
|
37
|
+
const allowed = (0, ip_range_builder_1.buildIpRangeList)(this.ssrfConfig.allowedIpRanges);
|
|
38
|
+
for (const issue of allowed.issues) {
|
|
39
|
+
this.logger.warn(`Invalid value '${issue.entry}' in N8N_SSRF_ALLOWED_IP_RANGES: ${issue.error}`);
|
|
40
|
+
}
|
|
41
|
+
this.allowedIps = allowed.list;
|
|
42
|
+
this.allowedHostnameMatcher = new hostname_matcher_1.HostnameMatcher(this.ssrfConfig.allowedHostnames);
|
|
43
|
+
}
|
|
44
|
+
async validateUrl(url) {
|
|
45
|
+
const parsed = this.tryParseUrl(url);
|
|
46
|
+
if (!parsed) {
|
|
47
|
+
this.events.emit('ssrf.blocked', {
|
|
48
|
+
phase: 'pre_flight',
|
|
49
|
+
reason: 'invalid_url',
|
|
50
|
+
durationMs: 0,
|
|
51
|
+
});
|
|
52
|
+
return (0, n8n_workflow_1.createResultError)(new Error(`Invalid URL: ${String(url)}`));
|
|
53
|
+
}
|
|
54
|
+
const result = await this.withEvents('pre_flight', async () => await this.lookupAndValidate(parsed.hostname, { all: true }));
|
|
55
|
+
return result.ok ? (0, n8n_workflow_1.createResultOk)(undefined) : result;
|
|
56
|
+
}
|
|
57
|
+
validateIp(ip) {
|
|
58
|
+
const family = this.getIpFamily(ip);
|
|
59
|
+
(0, node_assert_1.default)(family !== null, `Invalid IP address: ${ip}`);
|
|
60
|
+
if (this.allowedIps.check(ip, family)) {
|
|
61
|
+
return (0, n8n_workflow_1.createResultOk)(undefined);
|
|
62
|
+
}
|
|
63
|
+
if (this.blockedIps.check(ip, family)) {
|
|
64
|
+
return (0, n8n_workflow_1.createResultError)(new ssrf_blocked_ip_error_1.SsrfBlockedIpError(ip));
|
|
65
|
+
}
|
|
66
|
+
return (0, n8n_workflow_1.createResultOk)(undefined);
|
|
67
|
+
}
|
|
68
|
+
createSecureLookup() {
|
|
69
|
+
return async (hostname, options, onResult) => {
|
|
70
|
+
try {
|
|
71
|
+
const resolved = await this.secureLookupAsync(hostname, options);
|
|
72
|
+
if (options.all) {
|
|
73
|
+
onResult(null, resolved);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const first = resolved[0];
|
|
77
|
+
onResult(null, first.address, first.family);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
onResult((0, n8n_workflow_1.ensureError)(error), options.all ? [] : '', undefined);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
validateRedirectSync(url) {
|
|
85
|
+
const parsed = this.tryParseUrl(url);
|
|
86
|
+
if (!parsed)
|
|
87
|
+
return;
|
|
88
|
+
const { hostname } = parsed;
|
|
89
|
+
if (this.allowedHostnameMatcher.matches(hostname))
|
|
90
|
+
return;
|
|
91
|
+
const cleanIp = this.normalizeIpInHostname(hostname);
|
|
92
|
+
if ((0, node_net_1.isIP)(cleanIp)) {
|
|
93
|
+
const result = this.withEvents('redirect', () => this.validateIp(cleanIp));
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
throw result.error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
normalizeIpInHostname(hostname) {
|
|
100
|
+
return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
|
|
101
|
+
}
|
|
102
|
+
async secureLookupAsync(hostname, options) {
|
|
103
|
+
const result = await this.withEvents('connect_time', async () => await this.lookupAndValidate(hostname, options));
|
|
104
|
+
if (!result.ok) {
|
|
105
|
+
throw result.error;
|
|
106
|
+
}
|
|
107
|
+
return result.result;
|
|
108
|
+
}
|
|
109
|
+
async lookupAndValidate(hostname, options) {
|
|
110
|
+
const cleanIp = this.normalizeIpInHostname(hostname);
|
|
111
|
+
const ipFamily = (0, node_net_1.isIP)(cleanIp);
|
|
112
|
+
if (ipFamily) {
|
|
113
|
+
const result = this.validateIp(cleanIp);
|
|
114
|
+
if (!result.ok) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
return (0, n8n_workflow_1.createResultOk)([
|
|
118
|
+
{
|
|
119
|
+
address: cleanIp,
|
|
120
|
+
family: ipFamily,
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
const resolved = await this.dnsResolver.lookup(hostname, options);
|
|
125
|
+
(0, node_assert_1.default)(resolved.length > 0, `DNS lookup for ${hostname} returned no results`);
|
|
126
|
+
if (this.allowedHostnameMatcher.matches(hostname)) {
|
|
127
|
+
return (0, n8n_workflow_1.createResultOk)(resolved);
|
|
128
|
+
}
|
|
129
|
+
for (const ip of resolved) {
|
|
130
|
+
const result = this.validateIp(ip.address);
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return (0, n8n_workflow_1.createResultOk)(resolved);
|
|
136
|
+
}
|
|
137
|
+
withEvents(phase, fn) {
|
|
138
|
+
const start = performance.now();
|
|
139
|
+
const result = fn();
|
|
140
|
+
const emitAndReturn = (result) => {
|
|
141
|
+
const durationMs = performance.now() - start;
|
|
142
|
+
if (result.ok) {
|
|
143
|
+
this.events.emit('ssrf.allowed', { phase, durationMs });
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.events.emit('ssrf.blocked', {
|
|
147
|
+
phase,
|
|
148
|
+
reason: this.toReason(result.error),
|
|
149
|
+
durationMs,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
};
|
|
154
|
+
const emitAndRethrow = (error) => {
|
|
155
|
+
emitAndReturn((0, n8n_workflow_1.createResultError)((0, n8n_workflow_1.ensureError)(error)));
|
|
156
|
+
throw error;
|
|
157
|
+
};
|
|
158
|
+
return result instanceof Promise
|
|
159
|
+
? result.then(emitAndReturn, emitAndRethrow)
|
|
160
|
+
: emitAndReturn(result);
|
|
161
|
+
}
|
|
162
|
+
toReason(error) {
|
|
163
|
+
if (error instanceof ssrf_blocked_ip_error_1.SsrfBlockedIpError) {
|
|
164
|
+
return 'blocked_ip';
|
|
165
|
+
}
|
|
166
|
+
return 'dns_error';
|
|
167
|
+
}
|
|
168
|
+
tryParseUrl(url) {
|
|
169
|
+
try {
|
|
170
|
+
return url instanceof URL ? url : new URL(url);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
getIpFamily(ip) {
|
|
177
|
+
const version = (0, node_net_1.isIP)(ip);
|
|
178
|
+
if (version === 4)
|
|
179
|
+
return 'ipv4';
|
|
180
|
+
if (version === 6)
|
|
181
|
+
return 'ipv6';
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
exports.SsrfProtectionService = SsrfProtectionService;
|
|
186
|
+
exports.SsrfProtectionService = SsrfProtectionService = __decorate([
|
|
187
|
+
(0, di_1.Service)(),
|
|
188
|
+
__metadata("design:paramtypes", [config_1.SsrfProtectionConfig,
|
|
189
|
+
dns_1.DnsResolver,
|
|
190
|
+
backend_common_1.Logger])
|
|
191
|
+
], SsrfProtectionService);
|
|
192
|
+
//# sourceMappingURL=ssrf-protection.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf-protection.service.js","sourceRoot":"","sources":["../../src/ssrf/ssrf-protection.service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,22 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@n8n/backend-network",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"dist/**/*"
|
|
8
|
+
"dist/**/*",
|
|
9
|
+
"LICENSE_EE.md",
|
|
10
|
+
"LICENSE.md"
|
|
9
11
|
],
|
|
10
12
|
"dependencies": {
|
|
11
|
-
"
|
|
13
|
+
"axios": "1.16.1",
|
|
14
|
+
"cache-manager": "5.2.3",
|
|
15
|
+
"form-data": "4.0.4",
|
|
16
|
+
"http-proxy-agent": "7.0.2",
|
|
17
|
+
"https-proxy-agent": "7.0.6",
|
|
18
|
+
"iconv-lite": "0.6.3",
|
|
19
|
+
"proxy-from-env": "^1.1.0",
|
|
20
|
+
"qs": "6.15.2",
|
|
21
|
+
"reflect-metadata": "0.2.2",
|
|
22
|
+
"zod": "3.25.67",
|
|
23
|
+
"@n8n/backend-common": "1.27.1",
|
|
24
|
+
"@n8n/di": "0.14.0",
|
|
25
|
+
"@n8n/config": "2.25.1",
|
|
26
|
+
"@n8n/constants": "0.27.0",
|
|
27
|
+
"n8n-workflow": "2.27.0"
|
|
12
28
|
},
|
|
13
29
|
"devDependencies": {
|
|
30
|
+
"@types/proxy-from-env": "^1.0.4",
|
|
14
31
|
"@vitest/coverage-v8": "4.1.1",
|
|
32
|
+
"nock": "^14.0.14",
|
|
15
33
|
"vitest": "^4.1.1",
|
|
16
|
-
"
|
|
17
|
-
"@n8n/vitest-config": "1.
|
|
34
|
+
"vitest-mock-extended": "^3.1.0",
|
|
35
|
+
"@n8n/vitest-config": "1.15.0",
|
|
36
|
+
"@n8n/typescript-config": "1.6.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
39
|
+
"homepage": "https://n8n.io",
|
|
40
|
+
"author": {
|
|
41
|
+
"name": "Jan Oberhauser",
|
|
42
|
+
"email": "jan@n8n.io"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/n8n-io/n8n.git"
|
|
18
47
|
},
|
|
19
|
-
"license": "LicenseRef-n8n-sustainable-use",
|
|
20
48
|
"scripts": {
|
|
21
49
|
"clean": "rimraf dist .turbo",
|
|
22
50
|
"dev": "pnpm watch",
|
|
@@ -28,8 +56,8 @@
|
|
|
28
56
|
"lint": "eslint . --quiet",
|
|
29
57
|
"lint:fix": "eslint . --fix",
|
|
30
58
|
"watch": "tsc -p tsconfig.build.json --watch",
|
|
31
|
-
"test": "vitest run
|
|
32
|
-
"test:unit": "vitest run
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:unit": "vitest run",
|
|
33
61
|
"test:dev": "vitest --silent=false"
|
|
34
62
|
}
|
|
35
63
|
}
|