From 57bc28682eefcaf61d869c1ce2e50dbd7cf17d2f Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Sat, 4 Jul 2026 19:40:05 -0300 Subject: [PATCH 1/3] neutralize formula characters in csv exports --- apps/web/lib/analytics/utils/convert-to-csv.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/web/lib/analytics/utils/convert-to-csv.ts b/apps/web/lib/analytics/utils/convert-to-csv.ts index 6e8dfbe062b..71316d939b5 100644 --- a/apps/web/lib/analytics/utils/convert-to-csv.ts +++ b/apps/web/lib/analytics/utils/convert-to-csv.ts @@ -1,5 +1,9 @@ import { json2csv } from "json-2-csv"; +const FORMULA_TRIGGER_CHARS = /^[=+\-@\t\r]/; +const neutralizeFormula = (value: string) => + FORMULA_TRIGGER_CHARS.test(value) ? `'${value}` : value; + export const convertToCSV = (data: object[]) => { return json2csv(data, { parseValue(fieldValue, defaultParser) { @@ -11,6 +15,10 @@ export const convertToCSV = (data: object[]) => { return fieldValue.toISOString(); } + if (typeof fieldValue === "string") { + return defaultParser(neutralizeFormula(fieldValue)); + } + return defaultParser(fieldValue); }, }); From 604a09ccf887cf283ae5d38b1ec0dbc2fe28b981 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Sat, 4 Jul 2026 20:19:07 -0300 Subject: [PATCH 2/3] sanitizeFormulaInput --- .../actions/partners/update-partner-profile.ts | 16 +++++++++++++--- apps/web/lib/analytics/utils/convert-to-csv.ts | 8 -------- apps/web/lib/zod/schemas/partners.ts | 16 ++++++++++++++-- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/web/lib/actions/partners/update-partner-profile.ts b/apps/web/lib/actions/partners/update-partner-profile.ts index ddbe6eb04f6..13b88309d17 100644 --- a/apps/web/lib/actions/partners/update-partner-profile.ts +++ b/apps/web/lib/actions/partners/update-partner-profile.ts @@ -12,6 +12,7 @@ import { partnerProfileChangeHistoryLogSchema } from "@/lib/zod/schemas/partner- import { MAX_PARTNER_DESCRIPTION_LENGTH, PartnerProfileDetailsSchema, + sanitizeFormulaInput, } from "@/lib/zod/schemas/partners"; import { APP_DOMAIN_WITH_NGROK, @@ -29,13 +30,22 @@ import { authPartnerActionClient } from "../safe-action"; const updatePartnerProfileSchema = z .object({ - name: z.string().trim().min(1, "Name is required").optional(), + name: z + .string() + .trim() + .min(1, "Name is required") + .optional() + .transform(sanitizeFormulaInput), email: z.email().optional(), username: z.string().trim().toLowerCase().min(3).max(100).optional(), image: uploadedImageSchema.nullish(), - description: z.string().max(MAX_PARTNER_DESCRIPTION_LENGTH).nullish(), + description: z + .string() + .max(MAX_PARTNER_DESCRIPTION_LENGTH) + .nullish() + .transform(sanitizeFormulaInput), profileType: z.enum(PartnerProfileType).optional(), - companyName: z.string().nullish(), + companyName: z.string().nullish().transform(sanitizeFormulaInput), }) .extend(PartnerProfileDetailsSchema.partial().shape) .transform((data) => ({ diff --git a/apps/web/lib/analytics/utils/convert-to-csv.ts b/apps/web/lib/analytics/utils/convert-to-csv.ts index 71316d939b5..6e8dfbe062b 100644 --- a/apps/web/lib/analytics/utils/convert-to-csv.ts +++ b/apps/web/lib/analytics/utils/convert-to-csv.ts @@ -1,9 +1,5 @@ import { json2csv } from "json-2-csv"; -const FORMULA_TRIGGER_CHARS = /^[=+\-@\t\r]/; -const neutralizeFormula = (value: string) => - FORMULA_TRIGGER_CHARS.test(value) ? `'${value}` : value; - export const convertToCSV = (data: object[]) => { return json2csv(data, { parseValue(fieldValue, defaultParser) { @@ -15,10 +11,6 @@ export const convertToCSV = (data: object[]) => { return fieldValue.toISOString(); } - if (typeof fieldValue === "string") { - return defaultParser(neutralizeFormula(fieldValue)); - } - return defaultParser(fieldValue); }, }); diff --git a/apps/web/lib/zod/schemas/partners.ts b/apps/web/lib/zod/schemas/partners.ts index fb1e1431600..81dce59f0ed 100644 --- a/apps/web/lib/zod/schemas/partners.ts +++ b/apps/web/lib/zod/schemas/partners.ts @@ -749,6 +749,16 @@ const partnerImageSchema = z.union([ z.string().nullish(), // make image optional ]); +export const sanitizeFormulaInput = ( + value: T, +): T => { + if (typeof value !== "string") { + return value; + } + + return value.replace(/^[=+\-@\t\r]+/, "").trimStart() as T; +}; + export const onboardPartnerSchema = createPartnerSchema .omit({ username: true, @@ -757,10 +767,12 @@ export const onboardPartnerSchema = createPartnerSchema linkProps: true, }) .extend({ - name: z.string().min(1, "Name is required"), + name: z.string().min(1, "Name is required").transform(sanitizeFormulaInput), + description: + createPartnerSchema.shape.description.transform(sanitizeFormulaInput), image: partnerImageSchema, profileType: z.enum(PartnerProfileType).default("individual"), - companyName: z.string().nullish(), + companyName: z.string().nullish().transform(sanitizeFormulaInput), }) .refine( (data) => { From 960ae9ac606818478d5e39427f473e21ece478c6 Mon Sep 17 00:00:00 2001 From: Pedro Ladeira Date: Sun, 5 Jul 2026 15:12:41 -0300 Subject: [PATCH 3/3] code improvements --- apps/web/lib/actions/partners/update-partner-profile.ts | 3 ++- apps/web/lib/zod/schemas/partners.ts | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/actions/partners/update-partner-profile.ts b/apps/web/lib/actions/partners/update-partner-profile.ts index 13b88309d17..e07af1a35c9 100644 --- a/apps/web/lib/actions/partners/update-partner-profile.ts +++ b/apps/web/lib/actions/partners/update-partner-profile.ts @@ -35,7 +35,8 @@ const updatePartnerProfileSchema = z .trim() .min(1, "Name is required") .optional() - .transform(sanitizeFormulaInput), + .transform(sanitizeFormulaInput) + .refine((v) => v === undefined || v.length > 0, "Name is required"), email: z.email().optional(), username: z.string().trim().toLowerCase().min(3).max(100).optional(), image: uploadedImageSchema.nullish(), diff --git a/apps/web/lib/zod/schemas/partners.ts b/apps/web/lib/zod/schemas/partners.ts index 81dce59f0ed..4f5b43c673a 100644 --- a/apps/web/lib/zod/schemas/partners.ts +++ b/apps/web/lib/zod/schemas/partners.ts @@ -767,7 +767,11 @@ export const onboardPartnerSchema = createPartnerSchema linkProps: true, }) .extend({ - name: z.string().min(1, "Name is required").transform(sanitizeFormulaInput), + name: z + .string() + .min(1, "Name is required") + .transform(sanitizeFormulaInput) + .refine((v) => v.length > 0, "Name is required"), description: createPartnerSchema.shape.description.transform(sanitizeFormulaInput), image: partnerImageSchema,