Centralize outbound HTTP requests through SecureHttpClientService (#17779)
## Summary - Migrates all direct `axios` and `@nestjs/axios` `HttpService` usages across the server to go through `SecureHttpClientService`, which conditionally applies SSRF protection based on the `OUTBOUND_HTTP_SAFE_MODE_ENABLED` config flag - `SecureHttpClientService.getHttpClient()` now accepts optional `AxiosRequestConfig` (e.g., `baseURL`) so callers can configure their client while still getting protection - Adds `getInternalHttpClient()` for trusted same-server requests (e.g., REST-to-GraphQL proxy, code-interpreter downloading internal files) - Renames `getSecureAdapter` to `getSecureAxiosAdapter` for clarity - Captcha drivers now receive a pre-configured `AxiosInstance` from the module factory instead of creating their own ## Migrated services | Service | Previous | Risk level | |---------|----------|-----------| | `file-upload.service` | `HttpService` | High (user-provided image URLs) | | `code-interpreter-tool` | `HttpService` + direct adapter | High (user-provided file URLs) | | `search-help-center-tool` | `axios.post()` | Low (hardcoded endpoints) | | `http-tool` | Already migrated | High (user-provided URLs) | | `admin-panel.service` | `axios.get()` | Low (Docker Hub API) | | `sign-in-up.service` | `HttpService` | Medium (logo URL validation) | | `google-apis-scopes` | `HttpService` | Low (Google API) | | `geo-map.service` | `HttpService` | Low (Google Maps API) | | `telemetry.service` | `HttpService` | Low (telemetry endpoint) | | `rest-api.service` | `HttpService` | Internal (uses `getInternalHttpClient`) | | `create-company.service` | `axios.create()` | Low (Twenty companies API) | | `google-recaptcha.driver` | `axios.create()` | Low (Google reCAPTCHA) | | `turnstile.driver` | `axios.create()` | Low (Cloudflare Turnstile) | ## Test plan - [x] `npx nx typecheck twenty-server` passes - [x] `npx nx lint:diff-with-main twenty-server` passes - [x] Admin panel unit tests pass - [x] Secure adapter unit tests pass Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
+2
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/tool/services/secure-http-client.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
@@ -19,6 +20,7 @@ import { CreatePersonService } from 'src/modules/contact-creation-manager/servic
|
||||
CreateCompanyService,
|
||||
CreatePersonService,
|
||||
CreateCompanyAndPersonService,
|
||||
SecureHttpClientService,
|
||||
],
|
||||
exports: [CreateCompanyAndPersonService],
|
||||
})
|
||||
|
||||
+7
-5
@@ -1,7 +1,6 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import axios from 'axios';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
FieldActorSource,
|
||||
@@ -9,14 +8,13 @@ import {
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/tool/services/secure-http-client.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import {
|
||||
type CompanyToCreate,
|
||||
CreateCompanyService,
|
||||
} from 'src/modules/contact-creation-manager/services/create-company.service';
|
||||
|
||||
jest.mock('axios');
|
||||
|
||||
describe('CreateCompanyService', () => {
|
||||
let service: CreateCompanyService;
|
||||
let mockCompanyRepository: any;
|
||||
@@ -109,11 +107,15 @@ describe('CreateCompanyService', () => {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
(axios.create as jest.Mock).mockReturnValue(mockHttpService);
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
CreateCompanyService,
|
||||
{
|
||||
provide: SecureHttpClientService,
|
||||
useValue: {
|
||||
getHttpClient: jest.fn().mockReturnValue(mockHttpService),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import axios, { type AxiosInstance } from 'axios';
|
||||
import { type AxiosInstance } from 'axios';
|
||||
import uniqBy from 'lodash.uniqby';
|
||||
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from 'twenty-shared/utils';
|
||||
import { type DeepPartial, ILike } from 'typeorm';
|
||||
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/tool/services/secure-http-client.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
@@ -37,8 +38,9 @@ export class CreateCompanyService {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly secureHttpClientService: SecureHttpClientService,
|
||||
) {
|
||||
this.httpService = axios.create({
|
||||
this.httpService = this.secureHttpClientService.getHttpClient({
|
||||
baseURL: TWENTY_COMPANIES_BASE_URL,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user