ssrf hardening (#19963)
Hardened CalDav with new approach of wrapping axios ssrf http agent to fetch via `@lifeomic/axios-fetch` because `tsdav` only accept `fetch` override. Also Hardened test endpoint
This commit is contained in:
+39
@@ -11,6 +11,14 @@ jest.mock('axios-retry', () => ({
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@lifeomic/axios-fetch', () => ({
|
||||
buildAxiosFetch: jest.fn(() => jest.fn()),
|
||||
}));
|
||||
|
||||
import { buildAxiosFetch } from '@lifeomic/axios-fetch';
|
||||
|
||||
const mockBuildAxiosFetch = jest.mocked(buildAxiosFetch);
|
||||
|
||||
jest.mock(
|
||||
'src/engine/core-modules/secure-http-client/utils/resolve-and-validate-hostname.util',
|
||||
() => ({
|
||||
@@ -315,6 +323,37 @@ describe('SecureHttpClientService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSsrfSafeFetch', () => {
|
||||
beforeEach(() => {
|
||||
mockBuildAxiosFetch.mockClear();
|
||||
});
|
||||
|
||||
it('should return globalThis.fetch when safe mode is off', () => {
|
||||
const service = new SecureHttpClientService(createMockConfigService());
|
||||
|
||||
const result = service.createSsrfSafeFetch();
|
||||
|
||||
expect(result).toBe(globalThis.fetch);
|
||||
expect(mockBuildAxiosFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should wrap an SSRF-protected axios client when safe mode is on', () => {
|
||||
const service = new SecureHttpClientService(
|
||||
createMockConfigService({ OUTBOUND_HTTP_SAFE_MODE_ENABLED: true }),
|
||||
);
|
||||
|
||||
service.createSsrfSafeFetch();
|
||||
|
||||
expect(mockBuildAxiosFetch).toHaveBeenCalledTimes(1);
|
||||
const axiosClient = mockBuildAxiosFetch.mock.calls[0][0] as ReturnType<
|
||||
SecureHttpClientService['getHttpClient']
|
||||
>;
|
||||
|
||||
expect(axiosClient.defaults.httpAgent).toBeInstanceOf(http.Agent);
|
||||
expect(axiosClient.defaults.httpsAgent).toBeInstanceOf(https.Agent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logging interceptor', () => {
|
||||
it('should add a request interceptor when context is provided', () => {
|
||||
const service = new SecureHttpClientService(createMockConfigService());
|
||||
|
||||
+10
@@ -4,6 +4,8 @@ import axios, { type AxiosInstance, type CreateAxiosDefaults } from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { buildAxiosFetch } from '@lifeomic/axios-fetch';
|
||||
|
||||
import { createSsrfSafeAgent } from 'src/engine/core-modules/secure-http-client/utils/create-ssrf-safe-agent.util';
|
||||
import { resolveAndValidateHostname } from 'src/engine/core-modules/secure-http-client/utils/resolve-and-validate-hostname.util';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -101,6 +103,14 @@ export class SecureHttpClientService {
|
||||
return axios.create(config);
|
||||
}
|
||||
|
||||
createSsrfSafeFetch(): typeof globalThis.fetch {
|
||||
if (!this.isSafeModeEnabled()) {
|
||||
return globalThis.fetch;
|
||||
}
|
||||
|
||||
return buildAxiosFetch(this.getHttpClient()) as typeof globalThis.fetch;
|
||||
}
|
||||
|
||||
async getValidatedHost(hostnameOrUrl: string): Promise<string> {
|
||||
if (!this.isSafeModeEnabled()) {
|
||||
return hostnameOrUrl;
|
||||
|
||||
Reference in New Issue
Block a user