Prevent SSRF via IMAP/SMTP/CalDAV (#17973)

Prevents leaking of internal services by filtering out private IPs, same
way we do for webhooks
This commit is contained in:
neo773
2026-02-19 02:45:10 +05:30
committed by GitHub
parent 67074a7581
commit 7de565f70c
13 changed files with 182 additions and 12 deletions
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { CalDavClientProvider } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider';
import { CalDavGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service';
@Module({
imports: [TwentyConfigModule],
imports: [SecureHttpClientModule, TwentyConfigModule],
providers: [CalDavClientProvider, CalDavGetEventsService],
exports: [CalDavGetEventsService],
})
@@ -2,11 +2,16 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
import { CalDAVClient } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@Injectable()
export class CalDavClientProvider {
constructor(
private readonly secureHttpClientService: SecureHttpClientService,
) {}
public async getCalDavCalendarClient(
connectedAccount: Pick<
ConnectedAccountWorkspaceEntity,
@@ -20,14 +25,17 @@ export class CalDavClientProvider {
) {
throw new Error('Missing required CalDAV connection parameters');
}
const caldavClient = new CalDAVClient({
const serverUrl = await this.secureHttpClientService.getValidatedUrl(
connectedAccount.connectionParameters.CALDAV.host,
);
return new CalDAVClient({
username:
connectedAccount.connectionParameters.CALDAV.username ??
connectedAccount.handle,
password: connectedAccount.connectionParameters.CALDAV.password,
serverUrl: connectedAccount.connectionParameters.CALDAV.host,
serverUrl,
});
return caldavClient;
}
}
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
@@ -29,6 +30,7 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p
TypeOrmModule.forFeature([FeatureFlagEntity]),
EmailAliasManagerModule,
FeatureFlagModule,
SecureHttpClientModule,
WorkspaceDataSourceModule,
MessageParticipantManagerModule,
],
@@ -5,6 +5,7 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
import { CustomError, isDefined } from 'twenty-shared/utils';
import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { parseImapAuthenticationError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-authentication-error.util';
@@ -21,6 +22,10 @@ export class ImapClientProvider {
private static readonly CONNECTION_TIMEOUT_MS = 30000;
private static readonly GREETING_TIMEOUT_MS = 16000;
constructor(
private readonly secureHttpClientService: SecureHttpClientService,
) {}
async getClient(
connectedAccount: ConnectedAccountIdentifier,
): Promise<ImapFlow> {
@@ -66,8 +71,13 @@ export class ImapClientProvider {
);
}
const validatedImapHost =
await this.secureHttpClientService.getValidatedHost(
connectionParameters.IMAP?.host || '',
);
const client = new ImapFlow({
host: connectionParameters.IMAP?.host || '',
host: validatedImapHost,
port: connectionParameters.IMAP?.port || 993,
secure: connectionParameters.IMAP?.secure,
auth: {
@@ -1,8 +1,11 @@
import { Module } from '@nestjs/common';
import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module';
import { SmtpClientProvider } from './providers/smtp-client.provider';
@Module({
imports: [SecureHttpClientModule],
providers: [SmtpClientProvider],
exports: [SmtpClientProvider],
})
@@ -5,10 +5,15 @@ import { isDefined } from 'twenty-shared/utils';
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@Injectable()
export class SmtpClientProvider {
constructor(
private readonly secureHttpClientService: SecureHttpClientService,
) {}
public async getSmtpClient(
connectedAccount: Pick<
ConnectedAccountWorkspaceEntity,
@@ -21,8 +26,11 @@ export class SmtpClientProvider {
throw new Error('SMTP settings not configured for this account');
}
const validatedSmtpHost =
await this.secureHttpClientService.getValidatedHost(smtpParams.host);
const options: SMTPConnection.Options = {
host: smtpParams.host,
host: validatedSmtpHost,
port: smtpParams.port,
auth: {
user: smtpParams.username ?? connectedAccount.handle ?? '',