From fa9b5017e0fb0a013a561a733ec01bb08eb74ff5 Mon Sep 17 00:00:00 2001 From: DavisVT Date: Sun, 31 May 2026 06:52:02 +0100 Subject: [PATCH] feat: add bio sanitization helper for creator profiles - Add sanitizeBio utility to strip HTML, remove invisible unicode, and normalize whitespace - Apply sanitization as zod transform in UpsertCreatorProfileBodySchema - Add comprehensive unit tests --- .../creator-bio-sanitize.utils.test.ts | 33 +++++++++++++++++++ .../creator/creator-bio-sanitize.utils.ts | 17 ++++++++++ .../creator/creator-profile.schemas.ts | 2 ++ 3 files changed, 52 insertions(+) create mode 100644 src/modules/creator/creator-bio-sanitize.utils.test.ts create mode 100644 src/modules/creator/creator-bio-sanitize.utils.ts diff --git a/src/modules/creator/creator-bio-sanitize.utils.test.ts b/src/modules/creator/creator-bio-sanitize.utils.test.ts new file mode 100644 index 0000000..2bfcde7 --- /dev/null +++ b/src/modules/creator/creator-bio-sanitize.utils.test.ts @@ -0,0 +1,33 @@ +import { sanitizeBio } from './creator-bio-sanitize.utils'; + +describe('sanitizeBio', () => { + it('strips HTML tags from the bio', () => { + expect(sanitizeBio('Hello world!

How are you?

')).toBe( + 'Hello world! How are you?' + ); + }); + + it('removes invisible Unicode characters', () => { + expect( + sanitizeBio('Hello\u200Bworld\u200D!\uFEFFHow\u200Care you?') + ).toBe('Helloworld!Howare you?'); + }); + + it('normalizes whitespace to single spaces', () => { + expect(sanitizeBio('Hello world ! How are you?')).toBe( + 'Hello world ! How are you?' + ); + }); + + it('trims leading and trailing whitespace', () => { + expect(sanitizeBio(' Hello world! ')).toBe('Hello world!'); + }); + + it('handles all sanitization requirements together', () => { + expect( + sanitizeBio( + '
Hello\u200B world! \uFEFF How are you?
' + ) + ).toBe('Hello world! How are you?'); + }); +}); diff --git a/src/modules/creator/creator-bio-sanitize.utils.ts b/src/modules/creator/creator-bio-sanitize.utils.ts new file mode 100644 index 0000000..c540ab8 --- /dev/null +++ b/src/modules/creator/creator-bio-sanitize.utils.ts @@ -0,0 +1,17 @@ +export function sanitizeBio(bio: string): string { + let sanitized = bio; + + // Strip HTML tags + sanitized = sanitized.replace(/<[^>]*>/g, ''); + + // Remove invisible Unicode characters (zero-width spaces, control characters, etc.) + // This includes: + // - Control characters (C0 and C1) except for space (0x20), tab (0x09), line feed (0x0A), carriage return (0x0D) + // - Zero-width spaces, joiners, non-joiners, etc. + sanitized = sanitized.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200D\u2060\uFEFF]/g, ''); + + // Normalize whitespace + sanitized = sanitized.replace(/\s+/g, ' ').trim(); + + return sanitized; +} diff --git a/src/modules/creator/creator-profile.schemas.ts b/src/modules/creator/creator-profile.schemas.ts index d223dde..c7b5fcf 100644 --- a/src/modules/creator/creator-profile.schemas.ts +++ b/src/modules/creator/creator-profile.schemas.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { withCreatorSlugEmptyStringNormalization } from './creator-slug-input.utils'; import { normalizeSocialLinkUrl } from './creator-social-link-url.utils'; +import { sanitizeBio } from './creator-bio-sanitize.utils'; /** * Shared creator profile identifier schema for route params. @@ -67,6 +68,7 @@ export const UpsertCreatorProfileBodySchema = z.object({ .string() .trim() .max(1000, 'Bio must be at most 1000 characters') + .transform(sanitizeBio) .optional(), avatarUrl: z .string()