diff --git a/packages/destination-actions/src/destinations/pipedrive/createUpdatePerson/__tests__/index.test.ts b/packages/destination-actions/src/destinations/pipedrive/createUpdatePerson/__tests__/index.test.ts index f111694fa7b..b7eb789c736 100644 --- a/packages/destination-actions/src/destinations/pipedrive/createUpdatePerson/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/pipedrive/createUpdatePerson/__tests__/index.test.ts @@ -8,6 +8,20 @@ const PIPEDRIVE_API_KEY = 'random string' const PIPEDRIVE_DOMAIN = 'companydomain' const PERSON_ID = 33333 +describe('Pipedrive domain validation', () => { + it('should throw when domain contains URL injection characters', async () => { + await expect( + testDestination.testAuthentication({ apiToken: PIPEDRIVE_API_KEY, domain: 'attacker.com/path?x=' }) + ).rejects.toThrowError(/Invalid domain/) + }) + + it('should throw when domain contains @ injection', async () => { + await expect( + testDestination.testAuthentication({ apiToken: PIPEDRIVE_API_KEY, domain: 'attacker.com@legitimate' }) + ).rejects.toThrowError(/Invalid domain/) + }) +}) + describe('Pipedrive.createUpdatePerson', () => { it('should create person if none exists', async () => { const scope = nock(`https://${PIPEDRIVE_DOMAIN}.pipedrive.com/api/v1`) diff --git a/packages/destination-actions/src/destinations/pipedrive/index.ts b/packages/destination-actions/src/destinations/pipedrive/index.ts index d25e896ab4d..e177c330d22 100644 --- a/packages/destination-actions/src/destinations/pipedrive/index.ts +++ b/packages/destination-actions/src/destinations/pipedrive/index.ts @@ -2,6 +2,7 @@ import createUpdateOrganization from './createUpdateOrganization' import createUpdatePerson from './createUpdatePerson' import { defaultValues, DestinationDefinition } from '@segment/actions-core' import type { Settings } from './generated-types' +import { validateDomain } from './utils' import createUpdateActivity from './createUpdateActivity' @@ -59,6 +60,7 @@ const destination: DestinationDefinition = { } }, testAuthentication: (request, { settings }) => { + validateDomain(settings.domain) return request(`https://${settings.domain}.pipedrive.com/api/v1/users/me`) } }, diff --git a/packages/destination-actions/src/destinations/pipedrive/utils.ts b/packages/destination-actions/src/destinations/pipedrive/utils.ts index 581966830b2..2b2a26c020e 100644 --- a/packages/destination-actions/src/destinations/pipedrive/utils.ts +++ b/packages/destination-actions/src/destinations/pipedrive/utils.ts @@ -1,5 +1,15 @@ +import { InvalidAuthenticationError } from '@segment/actions-core' + type PayloadWithCustomFields = { custom_fields?: { [k: string]: unknown } } +export function validateDomain(domain: string): void { + if (!/^[a-zA-Z0-9-]+$/.test(domain)) { + throw new InvalidAuthenticationError( + 'Invalid domain. Domain must contain only alphanumeric characters and hyphens.' + ) + } +} + export function addCustomFieldsFromPayloadToEntity(payload: PayloadWithCustomFields, entity: E) { if (!payload.custom_fields) { return diff --git a/packages/destination-actions/src/destinations/qualtrics/__tests__/index.test.ts b/packages/destination-actions/src/destinations/qualtrics/__tests__/index.test.ts index 8ac4fea8a0f..12073ce6fcf 100644 --- a/packages/destination-actions/src/destinations/qualtrics/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/qualtrics/__tests__/index.test.ts @@ -42,5 +42,23 @@ describe('Qualtrics', () => { await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/401/) }) + + it('throw error when datacenter contains URL injection characters', async () => { + const authData = { + apiToken: 'VALID_API_TOKEN_VALUE', + datacenter: 'attacker.com/path?x=' + } + + await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/Invalid datacenter ID/) + }) + + it('throw error when datacenter contains @ injection', async () => { + const authData = { + apiToken: 'VALID_API_TOKEN_VALUE', + datacenter: 'attacker.com@legitimate' + } + + await expect(testDestination.testAuthentication(authData)).rejects.toThrowError(/Invalid datacenter ID/) + }) }) }) diff --git a/packages/destination-actions/src/destinations/qualtrics/index.ts b/packages/destination-actions/src/destinations/qualtrics/index.ts index d60d7613143..680005bfad8 100644 --- a/packages/destination-actions/src/destinations/qualtrics/index.ts +++ b/packages/destination-actions/src/destinations/qualtrics/index.ts @@ -6,6 +6,7 @@ import addContactToXmd from './addContactToXmd' import upsertContactTransaction from './upsertContactTransaction' import triggerXflowWorkflow from './triggerXflowWorkflow' import QualtricsApiClient from './qualtricsApiClient' +import { validateDatacenter } from './utils' const destination: DestinationDefinition = { name: 'Qualtrics', @@ -31,6 +32,7 @@ const destination: DestinationDefinition = { } }, testAuthentication: async (request: RequestClient, input) => { + validateDatacenter(input.settings.datacenter) const apiClient = new QualtricsApiClient(input.settings.datacenter, input.settings.apiToken, request) const response = await apiClient.whoaAmI() return response.userName !== undefined diff --git a/packages/destination-actions/src/destinations/qualtrics/qualtricsApiClient.ts b/packages/destination-actions/src/destinations/qualtrics/qualtricsApiClient.ts index 16990aee750..187fac4995b 100644 --- a/packages/destination-actions/src/destinations/qualtrics/qualtricsApiClient.ts +++ b/packages/destination-actions/src/destinations/qualtrics/qualtricsApiClient.ts @@ -125,7 +125,8 @@ export default class QualtricsApiClient { private request: RequestClient constructor(dc: string, apiToken: string, request: RequestClient) { - this.baseUrl = `https://${dc || 'iad1'}.qualtrics.com` + const datacenter = dc || 'iad1' + this.baseUrl = `https://${datacenter}.qualtrics.com` this.apiToken = apiToken this.request = request } diff --git a/packages/destination-actions/src/destinations/qualtrics/utils.ts b/packages/destination-actions/src/destinations/qualtrics/utils.ts index 13d2388c97a..035a8eb7dc3 100644 --- a/packages/destination-actions/src/destinations/qualtrics/utils.ts +++ b/packages/destination-actions/src/destinations/qualtrics/utils.ts @@ -1,3 +1,13 @@ +import { InvalidAuthenticationError } from '@segment/actions-core' + +export function validateDatacenter(datacenter: string): void { + if (!/^[a-zA-Z0-9-]+$/.test(datacenter)) { + throw new InvalidAuthenticationError( + 'Invalid datacenter ID. Datacenter must contain only alphanumeric characters and hyphens.' + ) + } +} + export function parsedEmbeddedData( data: { [key: string]: unknown } | undefined ): Record { @@ -7,11 +17,11 @@ export function parsedEmbeddedData( return } if (typeof data[key] === 'string') { - parsedData[key] = data[key] as string + parsedData[key] = data[key] } else if (typeof data[key] === 'number') { - parsedData[key] = data[key] as number + parsedData[key] = data[key] } else if (typeof data[key] === 'boolean') { - parsedData[key] = data[key] as boolean + parsedData[key] = data[key] } else { try { parsedData[key] = JSON.stringify(data)