feat(core): Use custom agent to handle http(s) proxies (#21264)

This commit is contained in:
Guillaume Jacquart
2025-10-29 09:43:09 +01:00
committed by GitHub
parent 2cb8e84358
commit 8987659813
6 changed files with 413 additions and 61 deletions
+1
View File
@@ -178,6 +178,7 @@
"sshpk": "1.18.0",
"swagger-ui-express": "5.0.1",
"syslog-client": "1.1.1",
"undici": "^7.16.0",
"uuid": "catalog:",
"validator": "13.7.0",
"ws": "8.17.1",
@@ -1,3 +1,4 @@
import type { OidcConfigDto } from '@n8n/api-types';
import type { Logger } from '@n8n/backend-common';
import { mockInstance, mockLogger } from '@n8n/backend-test-utils';
import type { GlobalConfig } from '@n8n/config';
@@ -5,20 +6,24 @@ import type { AuthIdentityRepository, SettingsRepository, UserRepository } from
import { Container } from '@n8n/di';
import { mock } from 'jest-mock-extended';
import type { Cipher, InstanceSettings } from 'n8n-core';
import * as client from 'openid-client';
import type { JwtService } from '@/services/jwt.service';
import type { UrlService } from '@/services/url.service';
import { EnvHttpProxyAgent } from 'undici';
import * as ssoHelpers from '../../sso-helpers';
import { OIDC_PREFERENCES_DB_KEY } from '../constants';
import { OidcService } from '../oidc.service.ee';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import type { OidcConfigDto } from '@n8n/api-types';
import { type ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { type ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import type { JwtService } from '@/services/jwt.service';
import type { UrlService } from '@/services/url.service';
jest.mock('undici', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
EnvHttpProxyAgent: jest.fn().mockImplementation(() => ({})),
}));
describe('OidcService', () => {
let oidcService: OidcService;
@@ -537,4 +542,86 @@ describe('OidcService', () => {
expect(user.email).toEqual('john.doe@test.com');
});
});
describe('proxy configuration', () => {
const originalEnv = process.env;
// Helper function to create a proper mock Response
const createMockResponse = () => {
const mockData = {
issuer: 'https://example.com',
authorization_endpoint: 'https://example.com/auth',
token_endpoint: 'https://example.com/token',
userinfo_endpoint: 'https://example.com/userinfo',
jwks_uri: 'https://example.com/jwks',
};
return new Response(JSON.stringify(mockData), {
status: 200,
// eslint-disable-next-line @typescript-eslint/naming-convention
headers: { 'content-type': 'application/json' },
});
};
beforeEach(() => {
// Reset environment before each test
process.env = { ...originalEnv };
// Reset the mock between tests
(EnvHttpProxyAgent as unknown as jest.Mock).mockClear();
});
afterEach(() => {
// Restore original environment after each test
process.env = originalEnv;
});
it.each([
{ envVar: 'HTTP_PROXY', value: 'http://proxy.example.com:8080' },
{ envVar: 'HTTPS_PROXY', value: 'https://proxy.example.com:8443' },
{ envVar: 'ALL_PROXY', value: 'http://all-proxy.example.com:8888' },
])('should instantiate EnvHttpProxyAgent when $envVar is set', async ({ envVar, value }) => {
// Set proxy environment variable
process.env[envVar] = value;
const discoveryUrl = new URL('https://example.com/.well-known/openid-configuration');
const clientId = 'test-client';
const clientSecret = 'test-secret';
global.fetch = jest.fn().mockResolvedValue(createMockResponse());
// Call the private method directly using type assertion
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
await (oidcService as any).createProxyAwareConfiguration(
discoveryUrl,
clientId,
clientSecret,
);
// Verify EnvHttpProxyAgent was instantiated
expect(EnvHttpProxyAgent).toHaveBeenCalled();
});
it('should not instantiate EnvHttpProxyAgent when no proxy env vars are set', async () => {
// Ensure no proxy env vars are set
delete process.env.HTTP_PROXY;
delete process.env.HTTPS_PROXY;
delete process.env.ALL_PROXY;
const discoveryUrl = new URL('https://example.com/.well-known/openid-configuration');
const clientId = 'test-client';
const clientSecret = 'test-secret';
global.fetch = jest.fn().mockResolvedValue(createMockResponse());
// Call the private method directly
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
await (oidcService as any).createProxyAwareConfiguration(
discoveryUrl,
clientId,
clientSecret,
);
// Should not instantiate EnvHttpProxyAgent when no proxy is configured
expect(EnvHttpProxyAgent).not.toHaveBeenCalled();
});
});
});
@@ -10,17 +10,13 @@ import {
type User,
UserRepository,
} from '@n8n/db';
import { OnPubSubEvent } from '@n8n/decorators';
import { Container, Service } from '@n8n/di';
import { randomUUID } from 'crypto';
import { Cipher, InstanceSettings } from 'n8n-core';
import { jsonParse, UserError } from 'n8n-workflow';
import * as client from 'openid-client';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { JwtService } from '@/services/jwt.service';
import { UrlService } from '@/services/url.service';
import { EnvHttpProxyAgent } from 'undici';
import {
getCurrentAuthenticationMethod,
@@ -30,8 +26,13 @@ import {
setCurrentAuthenticationMethod,
} from '../sso-helpers';
import { OIDC_CLIENT_SECRET_REDACTED_VALUE, OIDC_PREFERENCES_DB_KEY } from './constants';
import { OnPubSubEvent } from '@n8n/decorators';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
import { ProvisioningService } from '@/modules/provisioning.ee/provisioning.service.ee';
import { JwtService } from '@/services/jwt.service';
import { UrlService } from '@/services/url.service';
const DEFAULT_OIDC_CONFIG: OidcConfigDto = {
clientId: '',
@@ -422,7 +423,7 @@ export class OidcService {
newConfig.clientSecret = this.oidcConfig.clientSecret;
}
try {
const discoveredMetadata = await client.discovery(
const discoveredMetadata = await this.createProxyAwareConfiguration(
discoveryEndpoint,
newConfig.clientId,
newConfig.clientSecret,
@@ -484,6 +485,46 @@ export class OidcService {
} & OidcRuntimeConfig)
| undefined;
/**
* Creates a proxy-aware configuration for openid-client.
* This method configures customFetch to respect HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables.
*/
private async createProxyAwareConfiguration(
discoveryUrl: URL,
clientId: string,
clientSecret: string,
): Promise<client.Configuration> {
const configuration = await client.discovery(discoveryUrl, clientId, clientSecret);
// Check if proxy environment variables are set
const hasProxyConfig =
process.env.HTTP_PROXY ?? process.env.HTTPS_PROXY ?? process.env.ALL_PROXY;
if (hasProxyConfig) {
this.logger.debug('Configuring OIDC client with proxy support', {
HTTP_PROXY: process.env.HTTP_PROXY,
HTTPS_PROXY: process.env.HTTPS_PROXY,
NO_PROXY: process.env.NO_PROXY,
ALL_PROXY: process.env.ALL_PROXY,
});
// Create a proxy agent that automatically reads from environment variables
const proxyAgent = new EnvHttpProxyAgent();
// Configure customFetch to use the proxy agent
configuration[client.customFetch] = async (...args) => {
const [url, options] = args;
return await fetch(url, {
...options,
// @ts-expect-error - dispatcher is an undici-specific option not in standard fetch
dispatcher: proxyAgent,
});
};
}
return configuration;
}
private async getOidcConfiguration(): Promise<client.Configuration> {
const now = Date.now();
if (
@@ -496,7 +537,7 @@ export class OidcService {
) {
this.cachedOidcConfiguration = {
...this.oidcConfig,
configuration: client.discovery(
configuration: this.createProxyAwareConfiguration(
this.oidcConfig.discoveryEndpoint,
this.oidcConfig.clientId,
this.oidcConfig.clientSecret,
@@ -2,15 +2,21 @@ import type { SamlPreferences } from '@n8n/api-types';
import { mockInstance, mockLogger } from '@n8n/backend-test-utils';
import type { GlobalConfig } from '@n8n/config';
import { SettingsRepository } from '@n8n/db';
import type { UserRepository } from '@n8n/db';
import type { Settings } from '@n8n/db';
import type { UserRepository, Settings } from '@n8n/db';
import { Container } from '@n8n/di';
import axios from 'axios';
import type express from 'express';
import type { HttpProxyAgent } from 'http-proxy-agent';
import type { HttpsProxyAgent } from 'https-proxy-agent';
import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from 'n8n-core';
import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify';
import { SAML_PREFERENCES_DB_KEY } from '../constants';
import { InvalidSamlMetadataUrlError } from '../errors/invalid-saml-metadata-url.error';
import { InvalidSamlMetadataError } from '../errors/invalid-saml-metadata.error';
import { SamlValidator } from '../saml-validator';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import type { UrlService } from '@/services/url.service';
@@ -18,11 +24,6 @@ import * as samlHelpers from '@/sso.ee/saml/saml-helpers';
import { SamlService } from '@/sso.ee/saml/saml.service.ee';
import * as ssoHelpers from '@/sso.ee/sso-helpers';
import { SAML_PREFERENCES_DB_KEY } from '../constants';
import { InvalidSamlMetadataUrlError } from '../errors/invalid-saml-metadata-url.error';
import { InvalidSamlMetadataError } from '../errors/invalid-saml-metadata.error';
import { SamlValidator } from '../saml-validator';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
@@ -649,4 +650,202 @@ describe('SamlService', () => {
expect(settingsRepository.delete).toHaveBeenCalledWith({ key: SAML_PREFERENCES_DB_KEY });
});
});
describe('proxy configuration', () => {
const originalEnv = process.env;
const validMetadataXml =
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://saml.example.com/entityid" validUntil="2035-05-07T13:33:47.181Z">\n <md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">\n <md:KeyDescriptor use="signing">\n <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">\n <ds:X509Data>\n <ds:X509Certificate>MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV\nSzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4\nMjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQK\nDAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0\nRuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd\n4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3V\npwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b\n2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZ\nNfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsF\nAAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW\n5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4\nkhuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeX\nUjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8L\nr/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99M\nm0eo2USlSRTVl7QHRTuiuSThHpLKQQ==</ds:X509Certificate>\n </ds:X509Data>\n </ds:KeyInfo>\n </md:KeyDescriptor>\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>\n <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://mocksaml.com/api/saml/sso"/>\n <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mocksaml.com/api/saml/sso"/>\n </md:IDPSSODescriptor>\n</md:EntityDescriptor>';
beforeEach(() => {
// Reset environment before each test
process.env = { ...originalEnv };
jest.restoreAllMocks();
jest.spyOn(samlService, 'loadSamlify').mockResolvedValue(undefined);
});
afterEach(() => {
// Restore original environment after each test
process.env = originalEnv;
});
test('should use proxy when HTTP_PROXY environment variable is set for HTTP URLs', async () => {
// Set HTTP proxy environment variable
const proxyUrl = 'http://proxy.example.com:8080';
process.env.HTTP_PROXY = proxyUrl;
// Use an HTTP metadata URL
const metadataUrl = 'http://saml.example.com/metadata';
// Mock the preferences to include a metadataUrl
type SamlServicePrivate = { _samlPreferences: SamlPreferences };
(samlService as unknown as SamlServicePrivate)._samlPreferences = {
metadata: mockSamlConfig.metadata,
metadataUrl,
} as SamlPreferences;
// Mock axios response
mockedAxios.get.mockResolvedValue({
status: 200,
data: validMetadataXml,
});
// Mock validator
jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true);
const result = await samlService.fetchMetadataFromUrl();
expect(result).toBe(validMetadataXml);
// Verify that axios.get was called with the correct URL and agents
expect(mockedAxios.get).toHaveBeenCalledWith(
metadataUrl,
expect.objectContaining({
httpAgent: expect.any(Object),
httpsAgent: expect.any(Object),
}),
);
// Get the actual call arguments to verify the agents
const callArgs = mockedAxios.get.mock.calls[0];
if (!callArgs?.[1]) {
throw new Error('Expected axios.get to be called with arguments');
}
const { httpAgent, httpsAgent } = callArgs[1];
// Verify that both agents are created
expect(httpAgent).toBeDefined();
expect(httpsAgent).toBeDefined();
// Verify the httpAgent has proxy configuration
expect(httpAgent).toHaveProperty('proxy');
const httpProxyAgent = httpAgent as unknown as HttpProxyAgent<string>;
expect(httpProxyAgent.proxy).toBeDefined();
expect(httpProxyAgent.proxy.href).toBe(`${proxyUrl}/`);
// The httpsAgent should also have the proxy (both are created with same proxy)
expect(httpsAgent).toHaveProperty('proxy');
const httpsProxyAgent = httpsAgent as unknown as HttpsProxyAgent<string>;
expect(httpsProxyAgent.proxy.href).toBe(`${proxyUrl}/`);
});
test('should use proxy when HTTPS_PROXY environment variable is set for HTTPS URLs', async () => {
// Set HTTPS proxy environment variable
const proxyUrl = 'https://proxy.example.com:8080';
process.env.HTTPS_PROXY = proxyUrl;
// Use an HTTPS metadata URL
const metadataUrl = 'https://saml.example.com/metadata';
// Mock the preferences to include a metadataUrl
type SamlServicePrivate = { _samlPreferences: SamlPreferences };
(samlService as unknown as SamlServicePrivate)._samlPreferences = {
metadata: mockSamlConfig.metadata,
metadataUrl,
ignoreSSL: true,
} as SamlPreferences;
// Mock axios response
mockedAxios.get.mockResolvedValue({
status: 200,
data: validMetadataXml,
});
// Mock validator
jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true);
const result = await samlService.fetchMetadataFromUrl();
expect(result).toBe(validMetadataXml);
// Verify that axios.get was called with the correct URL and agents
expect(mockedAxios.get).toHaveBeenCalledWith(
metadataUrl,
expect.objectContaining({
httpAgent: expect.any(Object),
httpsAgent: expect.any(Object),
}),
);
// Get the actual call arguments to verify the agents
const callArgs = mockedAxios.get.mock.calls[0];
if (!callArgs?.[1]) {
throw new Error('Expected axios.get to be called with arguments');
}
const { httpAgent, httpsAgent } = callArgs[1];
// Verify that both agents are created
expect(httpAgent).toBeDefined();
expect(httpsAgent).toBeDefined();
// Verify the httpsAgent has proxy configuration
expect(httpsAgent).toHaveProperty('proxy');
const httpsProxyAgent = httpsAgent as unknown as HttpsProxyAgent<string>;
expect(httpsProxyAgent.proxy).toBeDefined();
expect(httpsProxyAgent.proxy.href).toBe(`${proxyUrl}/`);
// Verify that the httpsAgent has the correct rejectUnauthorized setting when ignoreSSL is true
// HttpsProxyAgent stores connection options in connectOpts, not options
const httpsAgentWithConnectOpts = httpsProxyAgent as unknown as {
connectOpts?: { rejectUnauthorized?: boolean };
};
expect(httpsAgentWithConnectOpts.connectOpts?.rejectUnauthorized).toBe(false);
// The httpAgent should also have the proxy (both are created with same proxy)
expect(httpAgent).toHaveProperty('proxy');
const httpProxyAgent = httpAgent as unknown as HttpProxyAgent<string>;
expect(httpProxyAgent.proxy.href).toBe(`${proxyUrl}/`);
});
test('should work without proxy when no proxy environment variables are set', async () => {
// Ensure no proxy env vars are set
delete process.env.HTTP_PROXY;
delete process.env.HTTPS_PROXY;
delete process.env.ALL_PROXY;
// Mock the preferences to include a metadataUrl
type SamlServicePrivate = { _samlPreferences: SamlPreferences };
(samlService as unknown as SamlServicePrivate)._samlPreferences = {
metadata: mockSamlConfig.metadata,
metadataUrl: 'https://saml.example.com/metadata',
ignoreSSL: false,
} as SamlPreferences;
// Mock axios response
mockedAxios.get.mockResolvedValue({
status: 200,
data: validMetadataXml,
});
// Mock validator
jest.spyOn(samlService['validator'], 'validateMetadata').mockResolvedValue(true);
const result = await samlService.fetchMetadataFromUrl();
expect(result).toBe(validMetadataXml);
// Verify that axios.get was called with agents
expect(mockedAxios.get).toHaveBeenCalledWith(
'https://saml.example.com/metadata',
expect.objectContaining({
httpAgent: expect.any(Object),
httpsAgent: expect.any(Object),
}),
);
// Get the actual call arguments to verify the agents are NOT proxy agents
const callArgs = mockedAxios.get.mock.calls[0];
if (!callArgs?.[1]) {
throw new Error('Expected axios.get to be called with arguments');
}
const { httpAgent, httpsAgent } = callArgs[1];
// When no proxy is configured, regular http/https Agents are created
// These should NOT have a proxy property
expect(httpAgent).not.toHaveProperty('proxy');
expect(httpsAgent).not.toHaveProperty('proxy');
});
});
});
+19 -10
View File
@@ -7,16 +7,11 @@ import { OnPubSubEvent } from '@n8n/decorators';
import { Container, Service } from '@n8n/di';
import axios from 'axios';
import type express from 'express';
import https from 'https';
import { InstanceSettings } from 'n8n-core';
import { createHttpProxyAgent, createHttpsProxyAgent, InstanceSettings } from 'n8n-core';
import { jsonParse, UnexpectedError } from 'n8n-workflow';
import { type IdentityProviderInstance, type ServiceProviderInstance } from 'samlify';
import type { BindingContext, PostBindingContext } from 'samlify/types/src/entity';
import { AuthError } from '@/errors/response-errors/auth.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UrlService } from '@/services/url.service';
import { SAML_PREFERENCES_DB_KEY } from './constants';
import { InvalidSamlMetadataUrlError } from './errors/invalid-saml-metadata-url.error';
import { InvalidSamlMetadataError } from './errors/invalid-saml-metadata.error';
@@ -34,7 +29,11 @@ import { SamlValidator } from './saml-validator';
import { getServiceProviderInstance } from './service-provider.ee';
import type { SamlLoginBinding, SamlUserAttributes } from './types';
import { isSsoJustInTimeProvisioningEnabled, reloadAuthenticationMethod } from '../sso-helpers';
import { AuthError } from '@/errors/response-errors/auth.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { PROVISIONING_PREFERENCES_DB_KEY } from '@/modules/provisioning.ee/constants';
import { UrlService } from '@/services/url.service';
@Service()
export class SamlService {
@@ -444,11 +443,21 @@ export class SamlService {
if (!this._samlPreferences.metadataUrl)
throw new BadRequestError('Error fetching SAML Metadata, no Metadata URL set');
try {
// TODO:SAML: this will not work once axios is upgraded to > 1.2.0 (see checkServerIdentity)
const agent = new https.Agent({
rejectUnauthorized: !this._samlPreferences.ignoreSSL,
// Create a proxy-aware HTTPS agent that respects HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
// environment variables while also supporting SSL certificate validation options
const httpsAgent = createHttpsProxyAgent(
null, // Uses proxy from environment variables
this._samlPreferences.metadataUrl,
{
rejectUnauthorized: !this._samlPreferences.ignoreSSL,
},
);
const httpAgent = createHttpProxyAgent(null, this._samlPreferences.metadataUrl);
const response = await axios.get(this._samlPreferences.metadataUrl, {
httpsAgent,
httpAgent,
});
const response = await axios.get(this._samlPreferences.metadataUrl, { httpsAgent: agent });
if (response.status === 200 && response.data) {
const xml = (await response.data) as string;
const validationResult = await this.validator.validateMetadata(xml);
+43 -28
View File
@@ -539,7 +539,7 @@ importers:
version: 4.0.7
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
dotenv:
specifier: 8.6.0
version: 8.6.0
@@ -564,7 +564,7 @@ importers:
dependencies:
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
devDependencies:
'@n8n/typescript-config':
specifier: workspace:*
@@ -1052,7 +1052,7 @@ importers:
version: 4.3.0
'@getzep/zep-cloud':
specifier: 1.0.12
version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621))
version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d))
'@getzep/zep-js':
specifier: 0.9.0
version: 0.9.0
@@ -1079,7 +1079,7 @@ importers:
version: 0.3.4(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)
'@langchain/community':
specifier: 'catalog:'
version: 0.3.50(a14f1c0904e0abadfde97956245c3242)
version: 0.3.50(a75c8af281af8c64873764a9c6c64007)
'@langchain/core':
specifier: 'catalog:'
version: 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))
@@ -1202,7 +1202,7 @@ importers:
version: 23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)
langchain:
specifier: 0.3.33
version: 0.3.33(ca377405f102dc2905a39d54ecb4d621)
version: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d)
lodash:
specifier: 'catalog:'
version: 4.17.21
@@ -1318,7 +1318,7 @@ importers:
version: link:../eslint-plugin-community-nodes
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
eslint:
specifier: 'catalog:'
version: 9.29.0(jiti@2.6.1)
@@ -1532,7 +1532,7 @@ importers:
version: 1.11.0
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
bcryptjs:
specifier: 2.4.3
version: 2.4.3
@@ -1722,6 +1722,9 @@ importers:
syslog-client:
specifier: 1.1.1
version: 1.1.1
undici:
specifier: ^7.16.0
version: 7.16.0
uuid:
specifier: 'catalog:'
version: 10.0.0
@@ -1881,7 +1884,7 @@ importers:
version: 9.42.1
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
callsites:
specifier: 'catalog:'
version: 3.1.0
@@ -2351,7 +2354,7 @@ importers:
version: link:../../../@n8n/utils
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
flatted:
specifier: 'catalog:'
version: 3.2.7
@@ -2608,7 +2611,7 @@ importers:
version: 1.1.4
axios:
specifier: 1.12.0
version: 1.12.0(debug@4.4.1)
version: 1.12.0(debug@4.4.3)
bowser:
specifier: 2.11.0
version: 2.11.0
@@ -20366,7 +20369,7 @@ snapshots:
'@gar/promisify@1.1.3':
optional: true
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621))':
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d))':
dependencies:
form-data: 4.0.4
node-fetch: 2.7.0(encoding@0.1.13)
@@ -20375,7 +20378,7 @@ snapshots:
zod: 3.25.67
optionalDependencies:
'@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))
langchain: 0.3.33(ca377405f102dc2905a39d54ecb4d621)
langchain: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d)
transitivePeerDependencies:
- encoding
@@ -21067,7 +21070,7 @@ snapshots:
- aws-crt
- encoding
'@langchain/community@0.3.50(a14f1c0904e0abadfde97956245c3242)':
'@langchain/community@0.3.50(a75c8af281af8c64873764a9c6c64007)':
dependencies:
'@browserbasehq/stagehand': 1.9.0(@playwright/test@1.56.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@16.6.1)(encoding@0.1.13)(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67)
'@ibm-cloud/watsonx-ai': 1.1.2
@@ -21079,7 +21082,7 @@ snapshots:
flat: 5.0.2
ibm-cloud-sdk-core: 5.3.2
js-yaml: 4.1.0
langchain: 0.3.33(ca377405f102dc2905a39d54ecb4d621)
langchain: 0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d)
langsmith: 0.3.55(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))
openai: 5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)
uuid: 10.0.0
@@ -21093,7 +21096,7 @@ snapshots:
'@aws-sdk/credential-provider-node': 3.808.0
'@azure/storage-blob': 12.26.0
'@browserbasehq/sdk': 2.6.0(encoding@0.1.13)
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621))
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d))
'@getzep/zep-js': 0.9.0
'@google-ai/generativelanguage': 3.4.0(encoding@0.1.13)
'@google-cloud/storage': 7.12.1(encoding@0.1.13)
@@ -22353,7 +22356,7 @@ snapshots:
'@rudderstack/rudder-sdk-node@2.1.4(tslib@2.8.1)':
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
axios-retry: 4.5.0(axios@1.12.0)
component-type: 2.0.0
join-component: 1.1.0
@@ -24937,7 +24940,7 @@ snapshots:
axios-retry@4.5.0(axios@1.12.0):
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
is-retry-allowed: 2.2.0
axios@1.12.0(debug@4.3.6):
@@ -24956,6 +24959,14 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.12.0(debug@4.4.3):
dependencies:
follow-redirects: 1.15.11(debug@4.4.3)
form-data: 4.0.4
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
b4a@1.6.7: {}
babel-jest@29.6.2(@babel/core@7.26.10):
@@ -25331,7 +25342,7 @@ snapshots:
bundlemon@3.1.0(typescript@5.9.2):
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
axios-retry: 4.5.0(axios@1.12.0)
brotli-size: 4.0.0
bundlemon-utils: 2.0.1
@@ -27717,6 +27728,10 @@ snapshots:
optionalDependencies:
debug: 4.4.1(supports-color@8.1.1)
follow-redirects@1.15.11(debug@4.4.3):
optionalDependencies:
debug: 4.4.3
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
@@ -28441,7 +28456,7 @@ snapshots:
'@types/debug': 4.1.12
'@types/node': 20.19.21
'@types/tough-cookie': 4.0.5
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
camelcase: 6.3.0
debug: 4.4.3
dotenv: 16.6.1
@@ -28451,7 +28466,7 @@ snapshots:
isstream: 0.1.2
jsonwebtoken: 9.0.2
mime-types: 2.1.35
retry-axios: 2.6.0(axios@1.12.0(debug@4.4.3))
retry-axios: 2.6.0(axios@1.12.0)
tough-cookie: 4.1.4
transitivePeerDependencies:
- supports-color
@@ -28529,7 +28544,7 @@ snapshots:
infisical-node@1.3.0:
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
dotenv: 16.3.1
tweetnacl: 1.0.3
tweetnacl-util: 0.15.1
@@ -29907,7 +29922,7 @@ snapshots:
kuler@2.0.0: {}
langchain@0.3.33(ca377405f102dc2905a39d54ecb4d621):
langchain@0.3.33(5cc28a029307bb3da1dcaf370c8a2b8d):
dependencies:
'@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))
'@langchain/openai': 0.6.16(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))
@@ -29930,7 +29945,7 @@ snapshots:
'@langchain/groq': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))(encoding@0.1.13)
'@langchain/mistralai': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))
'@langchain/ollama': 0.2.3(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)))
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
cheerio: 1.0.0
handlebars: 4.7.8
transitivePeerDependencies:
@@ -32074,7 +32089,7 @@ snapshots:
posthog-node@3.2.1:
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
rusha: 0.8.14
transitivePeerDependencies:
- debug
@@ -32779,9 +32794,9 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
retry-axios@2.6.0(axios@1.12.0(debug@4.4.3)):
retry-axios@2.6.0(axios@1.12.0):
dependencies:
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
retry-request@7.0.2(encoding@0.1.13):
dependencies:
@@ -33388,7 +33403,7 @@ snapshots:
asn1.js: 5.4.1
asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1)
asn1.js-rfc5280: 3.0.0
axios: 1.12.0(debug@4.4.1)
axios: 1.12.0(debug@4.4.3)
big-integer: 1.6.52
bignumber.js: 9.1.2
binascii: 0.0.2
@@ -34914,7 +34929,7 @@ snapshots:
vite-node@3.1.3(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@8.1.1)
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@20.19.21)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.89.2)(terser@5.16.1)(tsx@4.19.3)