From cc7178a1d41b6993ae140b926d16756238d147bf Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Thu, 6 Mar 2025 17:25:37 +0700 Subject: [PATCH] Improve rate limit on sendInvite (#8150) Signed-off-by: Andrey Sobolev --- server/account/src/__tests__/sanitize.spec.ts | 33 +++++++++++++++++++ server/account/src/utils.ts | 12 ++++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 server/account/src/__tests__/sanitize.spec.ts diff --git a/server/account/src/__tests__/sanitize.spec.ts b/server/account/src/__tests__/sanitize.spec.ts new file mode 100644 index 0000000000..ab5d2a6647 --- /dev/null +++ b/server/account/src/__tests__/sanitize.spec.ts @@ -0,0 +1,33 @@ +import { sanitizeEmail } from '../utils' + +describe('sanitizeEmail', () => { + it('should lowercase and trim email', () => { + expect(sanitizeEmail(' Test@Example.com ')).toBe('TestExample.com') + }) + + it('should remove special characters', () => { + expect(sanitizeEmail('test<>/@{}[]example.com')).toBe('testexample.com') + }) + + it('should remove dangerous protocols', () => { + expect(sanitizeEmail('javascript:test@example.com')).toBe('testexample.com') + expect(sanitizeEmail('mailto:test@example.com')).toBe('testexample.com') + expect(sanitizeEmail('http://test@example.com')).toBe('testexample.com') + }) + + it('should limit length to 40 characters', () => { + const longEmail = 'verylongemailthatismorethanfortycharacters@example.com' + expect(sanitizeEmail(longEmail).length).toBe(40) + }) + + it('should handle null or invalid input', () => { + expect(sanitizeEmail('')).toBe('') + expect(sanitizeEmail(null as any)).toBe('') + expect(sanitizeEmail(undefined as any)).toBe('') + }) + + it('should preserve valid email addresses', () => { + expect(sanitizeEmail('user.name+tag@example.com')).toBe('user.name+tagexample.com') + expect(sanitizeEmail('test-email@domain.co.uk')).toBe('test-emaildomain.co.uk') + }) +}) diff --git a/server/account/src/utils.ts b/server/account/src/utils.ts index 56c360aeb0..6a3541e7a9 100644 --- a/server/account/src/utils.ts +++ b/server/account/src/utils.ts @@ -1183,6 +1183,16 @@ export async function sendEmail (info: EmailInfo): Promise { }) } +export function sanitizeEmail (email: string): string { + if (email == null || typeof email !== 'string') return '' + const sanitizedEmail = email + .trim() + .replace(/[<>/\\@{}()[\]'"`]/g, '') // Remove special chars and quotes + .replace(/^(http|ssh|ftp|https|mailto|javascript|data|file):?\/?\/?\s*/i, '') // Remove potentially dangerous protocols + .slice(0, 40) + return sanitizedEmail +} + export async function getInviteEmail ( branding: Branding | null, email: string, @@ -1193,7 +1203,7 @@ export async function getInviteEmail ( ): Promise { const front = getFrontUrl(branding) const link = concatLink(front, `/login/join?inviteId=${inviteId}`) - const ws = (workspace.name !== '' ? workspace.name : workspace.url).replace(/[<>/]/g, '').slice(0, 40) + const ws = sanitizeEmail(workspace.name !== '' ? workspace.name : workspace.url) const lang = branding?.language return {