Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/cookieSyncManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@ export default function CookieSyncManager(
// It is optional but to simplify the code, we add it for all Trade
// // Desk cookie syncs.
const domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
// Add domain parameter for Trade Desk
const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain);

// Google Marketing Platform requires a base64-encoded MPID query parameter
const base64Mpid = moduleId === PARTNER_MODULE_IDS.DoubleclickDFP ? btoa(mpid) : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard base64 used instead of URL-safe base64

Medium Severity

Google's google_hm parameter requires web-safe base64 encoding (RFC 4648 Section 5), which uses - and _ instead of + and /. The btoa() function produces standard base64 which can contain +, /, and = padding. These characters are not URL-safe and the value is also not passed through encodeURIComponent before being appended to the URL. For typical numeric MPIDs the = padding will always appear, and +// could appear for certain MPID values, resulting in a malformed URL or a value Google cannot correctly decode.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0fe7403. Configure here.


const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain, base64Mpid);

self.performCookieSync(
fullUrl,
Expand Down
16 changes: 11 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,26 +207,32 @@ const createCookieSyncUrl = (
mpid: MPID,
pixelUrl: string,
redirectUrl?: string,
domain?: string
domain?: string,
base64Mpid?: string
): string => {
const modifiedPixelUrl = replaceAmpWithAmpersand(pixelUrl);
const modifiedDirectUrl = redirectUrl
? replaceAmpWithAmpersand(redirectUrl)
: null;

let url = replaceMPID(modifiedPixelUrl, mpid);

const redirect = modifiedDirectUrl
? replaceMPID(modifiedDirectUrl, mpid)
: '';

let fullUrl = url + encodeURIComponent(redirect);

if (domain) {
const separator = fullUrl.includes('?') ? '&' : '?';
fullUrl += `${separator}domain=${domain}`;
}


if (base64Mpid) {
const separator = fullUrl.includes('?') ? '&' : '?';
fullUrl += `${separator}google_hm=${base64Mpid}`;
}

return fullUrl;
};

Expand Down
86 changes: 86 additions & 0 deletions test/jest/cookieSyncManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,92 @@ describe('CookieSyncManager', () => {
);
});
});

describe('Google Marketing Platform (DoubleclickDFP) base64 MPID', () => {
it('should add base64-encoded MPID parameter for DoubleclickDFP (module ID 41)', () => {
const gmpPixelSettings: IPixelConfiguration = {
...pixelSettings,
moduleId: PARTNER_MODULE_IDS.DoubleclickDFP, // 41
pixelUrl: 'https://cm.g.doubleclick.net/pixel?google_nid=abc123',
redirectUrl: '',
};

const mockMPInstance = ({
_Store: {
webviewBridgeEnabled: false,
pixelConfigurations: [gmpPixelSettings],
},
_CookieConsentManager: { getNoFunctional: jest.fn().mockReturnValue(false) },
_Persistence: {
getPersistence: () => ({testMPID: {
csd: {}
}}),
},
_Consent: {
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
},
Identity: {
getCurrentUser: jest.fn().mockReturnValue({
getMPID: () => testMPID,
}),
},
} as unknown) as IMParticleWebSDKInstance;

const cookieSyncManager = new CookieSyncManager(mockMPInstance);
cookieSyncManager.performCookieSync = jest.fn();

cookieSyncManager.attemptCookieSync(testMPID, true);

expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
`https://cm.g.doubleclick.net/pixel?google_nid=abc123&google_hm=${btoa(testMPID)}`,
'41',
testMPID,
{},
);
});

it('should not add base64 MPID parameter for non-DoubleclickDFP partners', () => {
const appNexusPixelSettings: IPixelConfiguration = {
...pixelSettings,
moduleId: PARTNER_MODULE_IDS.AppNexus, // 50
pixelUrl: 'https://ib.adnxs.com/cookie_sync?adv=abc123',
redirectUrl: '',
};

const mockMPInstance = ({
_Store: {
webviewBridgeEnabled: false,
pixelConfigurations: [appNexusPixelSettings],
},
_CookieConsentManager: { getNoFunctional: jest.fn().mockReturnValue(false) },
_Persistence: {
getPersistence: () => ({testMPID: {
csd: {}
}}),
},
_Consent: {
isEnabledForUserConsent: jest.fn().mockReturnValue(true),
},
Identity: {
getCurrentUser: jest.fn().mockReturnValue({
getMPID: () => testMPID,
}),
},
} as unknown) as IMParticleWebSDKInstance;

const cookieSyncManager = new CookieSyncManager(mockMPInstance);
cookieSyncManager.performCookieSync = jest.fn();

cookieSyncManager.attemptCookieSync(testMPID, true);

expect(cookieSyncManager.performCookieSync).toHaveBeenCalledWith(
'https://ib.adnxs.com/cookie_sync?adv=abc123',
'50',
testMPID,
{},
);
});
});
});

describe('PARTNER_MODULE_IDS', () => {
Expand Down
Loading