diff --git a/src/matchers.ts b/src/matchers.ts index ee11e72..d2d5b30 100644 --- a/src/matchers.ts +++ b/src/matchers.ts @@ -18,10 +18,6 @@ const escapePattern = (pattern: string): string => * Matches field names in url form encoded data, or other types of * data similarly character delimited * - * NOTE: this can partially fail if a non-word character is found - * before the end of the value is reached, but in that case - * it will still partially mask the value - * * @example * // when masked: 'password=mask' * formEncodedMatcher('password=foo') @@ -62,7 +58,7 @@ const formEncodedMatcher: DataSanitizationMatcher = ( 'gi', ); } - return new RegExp(`(\\w*${escaped}\\w*[=:])(?:\\W?.*?)([\\W]|$)`, 'gi'); + return new RegExp(`(\\w*${escaped}\\w*[=:])[^&]*(&|$)`, 'gi'); }; /** diff --git a/test/matchers.test.ts b/test/matchers.test.ts index 2577af6..4ac4967 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -49,6 +49,21 @@ describe('DataSanitizationMatchers', () => { expect(allMatches[0]?.[1]).toEqual('password:'); }); + it('should match form values containing non-delimiter punctuation', () => { + // Arrange + const matcher = formEncodedMatcher('password'); + const testData = 'password=abc-123%2Ba/b.c:z+q&username=mark'; + + // Act + const allMatches = [...testData.matchAll(matcher)]; + + // Assert + expect(allMatches.length).toBe(1); + expect(allMatches[0]?.[0]).toEqual('password=abc-123%2Ba/b.c:z+q&'); + expect(allMatches[0]?.[1]).toEqual('password='); + expect(allMatches[0]?.[2]).toEqual('&'); + }); + it('should match case-insensitively', () => { // Arrange const matcher = formEncodedMatcher('password'); @@ -109,6 +124,18 @@ describe('DataSanitizationMatchers', () => { // Assert expect(result).toBe(''); }); + + it('should produce a removal regex that removes punctuated values', () => { + // Arrange + const matcher = formEncodedMatcher('password', true); + const testData = 'password=abc-123%2Ba/b.c:z+q&username=mark'; + + // Act + const result = testData.replace(matcher, ''); + + // Assert + expect(result).toBe('username=mark'); + }); }); describe('jsonMatcher', () => { diff --git a/test/replacers.test.ts b/test/replacers.test.ts index fa434f4..ea9033f 100644 --- a/test/replacers.test.ts +++ b/test/replacers.test.ts @@ -130,6 +130,20 @@ describe('DataSanitizationReplacers', () => { expect(result.password).toEqual(DEFAULT_PATTERN_MASK); expect(result.username).toEqual('bar'); }); + + it('should fully mask form values containing non-delimiter punctuation', () => { + // Arrange + const testData = + 'password=abc-123&token=a%2Bb%2Fc&secret=a.b:c+z/9&username=mark'; + + // Act + const result = stringReplacer(testData) as string; + + // Assert + expect(result).toBe( + `password=${DEFAULT_PATTERN_MASK}&token=${DEFAULT_PATTERN_MASK}&secret=${DEFAULT_PATTERN_MASK}&username=mark`, + ); + }); }); describe('removal', () => { @@ -188,6 +202,20 @@ describe('DataSanitizationReplacers', () => { // Assert expect(result).toBe(''); }); + + it('should remove complete form values containing non-delimiter punctuation', () => { + // Arrange + const testData = + 'password=abc-123&token=a%2Bb%2Fc&secret=a.b:c+z/9&username=mark'; + + // Act + const result = stringReplacer(testData, { + removeMatches: true, + }) as string; + + // Assert + expect(result).toBe('username=mark'); + }); }); describe('options', () => {