diff --git a/packages/validators/package.json b/packages/validators/package.json index 94c1df3..2dc4757 100644 --- a/packages/validators/package.json +++ b/packages/validators/package.json @@ -1,6 +1,6 @@ { "name": "@opengovsg/starter-kitty-validators", - "version": "1.1.0", + "version": "1.1.1", "main": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ diff --git a/packages/validators/src/__tests__/email.test.ts b/packages/validators/src/__tests__/email.test.ts index 80c8232..07d1ed6 100644 --- a/packages/validators/src/__tests__/email.test.ts +++ b/packages/validators/src/__tests__/email.test.ts @@ -76,6 +76,20 @@ describe('EmailValidator with default options', () => { expect(() => schema.parse('postmaster@[123.123.123.123]')).toThrowError( ZodError, ) + + // Encoded-word + // https://portswigger.net/research/splitting-the-email-atom + expect(() => + schema.parse('=?iso-8859-1?q?=61=62=63?=collab@psres.net'), + ).toThrowError(ZodError) + + expect(() => + schema.parse('=?utf-7?b?JkFHWUFid0J2QUdJQVlRQnkt?=@psres.net'), + ).toThrowError(ZodError) + + expect(() => + schema.parse('=?x?q?collab=40invalid.com=3e=00?=open.gov.sg'), + ).toThrowError(ZodError) }) it('should clean up unnecessary whitespace', () => { diff --git a/packages/validators/src/email/consts.ts b/packages/validators/src/email/consts.ts index be1b1ca..b00b6d2 100644 --- a/packages/validators/src/email/consts.ts +++ b/packages/validators/src/email/consts.ts @@ -1,2 +1,3 @@ export const MAX_LOCAL_LENGTH = 64 export const MAX_DOMAIN_LENGTH = 255 +export const ENCODED_WORD_REGEX = /=[?].+[?]=/ diff --git a/packages/validators/src/email/utils.ts b/packages/validators/src/email/utils.ts index 9cae6a9..5faab50 100644 --- a/packages/validators/src/email/utils.ts +++ b/packages/validators/src/email/utils.ts @@ -1,6 +1,10 @@ import { ParsedMailbox } from 'email-addresses' -import { MAX_DOMAIN_LENGTH, MAX_LOCAL_LENGTH } from './consts' +import { + ENCODED_WORD_REGEX, + MAX_DOMAIN_LENGTH, + MAX_LOCAL_LENGTH, +} from './consts' import { ParsedEmailValidatorOptions } from './options' export const isValidEmail = ( @@ -17,6 +21,11 @@ export const isValidEmail = ( if (whitelisted.length === 0) { return true } + + if (ENCODED_WORD_REGEX.test(email.address)) { + return false + } + return isWhitelistedDomain(domain, whitelisted) }