diff --git a/apps/web/lib/api-logs/capture-request-log.ts b/apps/web/lib/api-logs/capture-request-log.ts index 7cf1cdd059..80eed2eacf 100644 --- a/apps/web/lib/api-logs/capture-request-log.ts +++ b/apps/web/lib/api-logs/capture-request-log.ts @@ -2,6 +2,10 @@ import { WorkspaceWithUsers } from "@/lib/types"; import { TokenCacheItem } from "../auth/token-cache"; import { Session } from "../auth/utils"; import { HTTP_MUTATION_METHODS, ROUTE_PATTERNS } from "./constants"; +import { + maskSensitiveFields, + SENSITIVE_RESPONSE_FIELDS_BY_ROUTE, +} from "./mask-sensitive-fields"; import { recordApiLog } from "./record-api-log"; // Precompile route patterns into regexes at module load @@ -79,6 +83,19 @@ export async function captureRequestLog({ responseBody = await responseClone.json(); } catch {} + // Mask sensitive fields in the response body + if (responseBody) { + const sensitiveResponseFields = + SENSITIVE_RESPONSE_FIELDS_BY_ROUTE[routePattern]; + + if (sensitiveResponseFields) { + responseBody = maskSensitiveFields({ + body: responseBody, + keys: sensitiveResponseFields, + }); + } + } + return await recordApiLog({ workspaceId: workspace.id, method: req.method, diff --git a/apps/web/lib/api-logs/mask-sensitive-fields.ts b/apps/web/lib/api-logs/mask-sensitive-fields.ts new file mode 100644 index 0000000000..cc46b5c009 --- /dev/null +++ b/apps/web/lib/api-logs/mask-sensitive-fields.ts @@ -0,0 +1,71 @@ +const FULL_MASK = "***"; +const MIDDLE_MASK = "*****"; +const MIN_HIDDEN_CHARS = 4; +const LAST_VISIBLE_CHARS = 6; + +// Map of route pattern -> response body fields that should be masked before logging. +export const SENSITIVE_RESPONSE_FIELDS_BY_ROUTE = { + "/tokens/embed/referrals": ["publicToken"], +} as const; + +// Stripe-style partial mask: visible prefix through the last `_` (e.g. `sk_live_`), +// a fixed middle mask, and the last 6 characters (e.g. `sk_live_*****xyz123`). +// Values without enough characters between prefix and suffix fully mask. +export function maskSensitiveValue(value: unknown): string { + if (typeof value !== "string" || value.length === 0) { + return FULL_MASK; + } + + const suffixLen = Math.min(LAST_VISIBLE_CHARS, value.length); + const suffixStart = value.length - suffixLen; + + const lastUnderscore = value.lastIndexOf("_"); + const prefixEnd = lastUnderscore >= 0 ? lastUnderscore + 1 : 0; + + if (prefixEnd > suffixStart) { + return FULL_MASK; + } + + if (suffixStart - prefixEnd < MIN_HIDDEN_CHARS) { + return FULL_MASK; + } + + const prefix = value.slice(0, prefixEnd); + const suffix = value.slice(suffixStart); + + return `${prefix}${MIDDLE_MASK}${suffix}`; +} + +// Recursively mask the given keys in an object/array. Returns a new value and +// does not mutate the input. Non-object values are returned as-is. +export function maskSensitiveFields({ + body, + keys, +}: { + body: T; + keys: string[]; +}): T { + if (!body || keys.length === 0) { + return body; + } + + const keySet = new Set(keys); + + const mask = (value: unknown): unknown => { + if (Array.isArray(value)) { + return value.map(mask); + } + + if (value && typeof value === "object") { + const result: Record = {}; + for (const [k, v] of Object.entries(value)) { + result[k] = keySet.has(k) ? maskSensitiveValue(v) : mask(v); + } + return result; + } + + return value; + }; + + return mask(body) as T; +} diff --git a/apps/web/lib/integrations/hubspot/track-lead.ts b/apps/web/lib/integrations/hubspot/track-lead.ts index 99b14be0ac..d1e3edb391 100644 --- a/apps/web/lib/integrations/hubspot/track-lead.ts +++ b/apps/web/lib/integrations/hubspot/track-lead.ts @@ -148,7 +148,8 @@ export const trackHubSpotLeadEvent = async ({ } if ( - contactInfo.properties.lifecyclestage !== settings.leadLifecycleStageId + contactInfo.properties.lifecyclestage?.toLowerCase() !== + settings.leadLifecycleStageId?.toLowerCase() ) { return `Unknown contact lifecyclestage ${contactInfo.properties.lifecyclestage}. Expected ${settings.leadLifecycleStageId}.`; } diff --git a/apps/web/lib/integrations/hubspot/track-sale.ts b/apps/web/lib/integrations/hubspot/track-sale.ts index 3228450568..1046a25c05 100644 --- a/apps/web/lib/integrations/hubspot/track-sale.ts +++ b/apps/web/lib/integrations/hubspot/track-sale.ts @@ -28,7 +28,9 @@ export const trackHubSpotSaleEvent = async ({ return `Unknown propertyName ${propertyName}. Expected dealstage.`; } - if (propertyValue !== settings.closedWonDealStageId) { + if ( + propertyValue.toLowerCase() !== settings.closedWonDealStageId?.toLowerCase() + ) { return `Unknown propertyValue ${propertyValue}. Expected ${settings.closedWonDealStageId}.`; }