1+ import { assert } from "chai" ;
2+ import { describe , it } from "mocha" ;
3+ import * as crypto from "crypto" ;
4+ import { sanitizeUrl , cleanUpUrlParams , cleanUrlSensitiveDataFromQuery , cleanUrlSensitiveDataFromValue } from "./logSanitizer" ;
5+
6+ describe ( "sanitizeUrl" , ( ) => {
7+ it ( "should remove sensitive data from query parameters" , ( ) => {
8+ const url = "https://example.com/path?client_secret=abc&token=xyz&other=123" ;
9+ const sanitizedUrl = sanitizeUrl ( url ) ;
10+ assert . equal ( sanitizedUrl , "https://example.com/path?client_secret=***&token=***&other=123" ) ;
11+ } ) ;
12+
13+ it ( "should remove jwt data from query parameters" , async ( ) => {
14+ const jwt = await generateTestJwt ( { testJwt : true } , "test_secret" ) ;
15+ const url = `https://example.com/path?test=${ jwt } &token=xyz&other=123` ;
16+ const sanitizedUrl = sanitizeUrl ( url ) ;
17+ assert . equal ( sanitizedUrl , "https://example.com/path?test=***&token=***&other=123" ) ;
18+ } ) ;
19+
20+ it ( "should remove jwt data from query part parameters" , async ( ) => {
21+ const jwt = await generateTestJwt ( { testJwt : true } , "test_secret" ) ;
22+ const url = `https://example.com/path?test=Bearer+${ jwt } &token=xyz&other=123` ;
23+ const sanitizedUrl = sanitizeUrl ( url ) ;
24+ assert . equal ( sanitizedUrl , "https://example.com/path?test=Bearer+***&token=***&other=123" ) ;
25+ } ) ;
26+
27+ it ( "should remove sensitive data from hash parameters" , ( ) => {
28+ const url = "https://example.com/path#client_secret=abc&token=xyz&other=123" ;
29+ const sanitizedUrl = sanitizeUrl ( url ) ;
30+ assert . equal ( sanitizedUrl , "https://example.com/path#client_secret=***&token=***&other=***" ) ;
31+ } ) ;
32+
33+ it ( "should handle URLs without sensitive data" , ( ) => {
34+ const url = "https://example.com/path?other=123" ;
35+ const sanitizedUrl = sanitizeUrl ( url ) ;
36+ assert . equal ( sanitizedUrl , "https://example.com/path?other=123" ) ;
37+ } ) ;
38+
39+ it ( "should handle URLs with only allowed parameters in hash" , ( ) => {
40+ const url = "https://example.com/path#state=abc&session_state=xyz&client_secret=abc" ;
41+ const sanitizedUrl = sanitizeUrl ( url ) ;
42+ assert . equal ( sanitizedUrl , "https://example.com/path#state=abc&session_state=xyz&client_secret=***" ) ;
43+ } ) ;
44+
45+ it ( "should handle null or undefined URLs" , ( ) => {
46+ assert . equal ( sanitizeUrl ( null ) , null ) ;
47+ assert . equal ( sanitizeUrl ( undefined ) , undefined ) ;
48+ } ) ;
49+
50+ it ( "should handle empty URLs" , ( ) => {
51+ assert . equal ( sanitizeUrl ( "" ) , "" ) ;
52+ } ) ;
53+ } ) ;
54+
55+
56+ describe ( "cleanUpUrlParams" , ( ) => {
57+ it ( "should replace sensitive parameters with ***" , ( ) => {
58+ const url = "https://example.com/path#client_secret=abc&token=xyz&other=123" ;
59+ const cleanedUrl = cleanUpUrlParams ( url ) ;
60+ assert . equal ( cleanedUrl , "https://example.com/path#client_secret=***&token=***&other=***" ) ;
61+ } ) ;
62+
63+ it ( "should leave allowed parameters unchanged" , ( ) => {
64+ const url = "https://example.com/path#state=abc&session_state=xyz&client_secret=abc" ;
65+ const cleanedUrl = cleanUpUrlParams ( url ) ;
66+ assert . equal ( cleanedUrl , "https://example.com/path#state=abc&session_state=xyz&client_secret=***" ) ;
67+ } ) ;
68+
69+ it ( "should handle URLs without hash" , ( ) => {
70+ const url = "https://example.com/path" ;
71+ const cleanedUrl = cleanUpUrlParams ( url ) ;
72+ assert . equal ( cleanedUrl , "https://example.com/path" ) ;
73+ } ) ;
74+
75+ it ( "should handle null or undefined URLs" , ( ) => {
76+ assert . equal ( cleanUpUrlParams ( null ) , null ) ;
77+ assert . equal ( cleanUpUrlParams ( undefined ) , undefined ) ;
78+ } ) ;
79+
80+ it ( "should handle empty URLs" , ( ) => {
81+ assert . equal ( cleanUpUrlParams ( "" ) , "" ) ;
82+ } ) ;
83+ } ) ;
84+
85+ describe ( "cleanUrlSensitiveDataFromQuery" , ( ) => {
86+ it ( "should replace sensitive query parameters with ***" , ( ) => {
87+ const url = "https://example.com/path?client_secret=abc&token=xyz&other=123" ;
88+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
89+ assert . equal ( cleanedUrl , "https://example.com/path?client_secret=***&token=***&other=123" ) ;
90+ } ) ;
91+
92+ it ( "should handle URLs without query parameters" , ( ) => {
93+ const url = "https://example.com/path" ;
94+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
95+ assert . equal ( cleanedUrl , "https://example.com/path" ) ;
96+ } ) ;
97+
98+ it ( "should handle null or undefined URLs" , ( ) => {
99+ assert . equal ( cleanUrlSensitiveDataFromQuery ( null ) , null ) ;
100+ assert . equal ( cleanUrlSensitiveDataFromQuery ( undefined ) , undefined ) ;
101+ } ) ;
102+
103+ it ( "should handle empty URLs" , ( ) => {
104+ assert . equal ( cleanUrlSensitiveDataFromQuery ( "" ) , "" ) ;
105+ } ) ;
106+
107+ it ( "should handle complex URLs with multiple parameters" , ( ) => {
108+ const url = "https://example.com/api/v1?client_secret=abc123&api_key=xyz789&user=john&password=pass123&normal=value" ;
109+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
110+ assert . equal ( cleanedUrl , "https://example.com/api/v1?client_secret=***&api_key=xyz789&user=***&password=***&normal=value" ) ;
111+ } ) ;
112+
113+ it ( "should handle URLs with encoded characters" , ( ) => {
114+ const url = "https://example.com/path?token=abc%26xyz&user_name=john%20doe" ;
115+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
116+ assert . equal ( cleanedUrl , "https://example.com/path?token=***&user_name=***" ) ;
117+ } ) ;
118+
119+ it ( "should handle special cases like access_token and user_name" , ( ) => {
120+ const url = "https://example.com/oauth?access_token=abc123&user_name=john" ;
121+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
122+ assert . equal ( cleanedUrl , "https://example.com/oauth?access_token=***&user_name=***" ) ;
123+ } ) ;
124+
125+ it ( "should handle malformed URLs by using fallback mechanism" , ( ) => {
126+ const url = "invalid://url with spaces?token=abc" ;
127+ const cleanedUrl = cleanUrlSensitiveDataFromQuery ( url ) ;
128+ // Should still sanitize using regex fallback
129+ assert . equal ( cleanedUrl , "invalid://url with spaces?token=***" ) ;
130+ } ) ;
131+ } ) ;
132+
133+ describe ( "cleanUrlSensitiveDataFromValue" , ( ) => {
134+ it ( "should replace sensitive data in header values with ***" , ( ) => {
135+ const dataValue = "client_secret=abc&token=xyz&other=123" ;
136+ const cleanedValue = cleanUrlSensitiveDataFromValue ( dataValue ) ;
137+ assert . equal ( cleanedValue , "client_secret=***&token=***&other=123" ) ;
138+ } ) ;
139+
140+ it ( "should replace jwt data in header values with ***" , async ( ) => {
141+ const dataValue = `test=${ await generateTestJwt ( { testJwt : true } , "test_secret" ) } &token=xyz&other=123` ;
142+ const cleanedValue = cleanUrlSensitiveDataFromValue ( dataValue ) ;
143+ assert . equal ( cleanedValue , "test=***&token=***&other=123" ) ;
144+ } ) ;
145+
146+ it ( "should replace all jwt data in header values with ***" , async ( ) => {
147+ const dataValue = `${ await generateTestJwt ( { testJwt : true } , "test_secret" ) } ` ;
148+ const cleanedValue = cleanUrlSensitiveDataFromValue ( dataValue ) ;
149+ assert . equal ( cleanedValue , "***" ) ;
150+ } ) ;
151+
152+ it ( "should handle values without sensitive data" , ( ) => {
153+ const dataValue = "other=123" ;
154+ const cleanedValue = cleanUrlSensitiveDataFromValue ( dataValue ) ;
155+ assert . equal ( cleanedValue , "other=123" ) ;
156+ } ) ;
157+
158+ it ( "should handle null or undefined values" , ( ) => {
159+ assert . equal ( cleanUrlSensitiveDataFromValue ( null ) , null ) ;
160+ assert . equal ( cleanUrlSensitiveDataFromValue ( undefined ) , undefined ) ;
161+ } ) ;
162+
163+ it ( "should handle empty values" , ( ) => {
164+ assert . equal ( cleanUrlSensitiveDataFromValue ( "" ) , "" ) ;
165+ } ) ;
166+ } ) ;
167+
168+ async function generateTestJwt ( payload , secret , header = { alg : "HS256" , typ : "JWT" } ) : Promise < string > {
169+ const headerEncoded = stringToBase64Url ( JSON . stringify ( header ) ) ;
170+ const payloadEncoded = stringToBase64Url ( JSON . stringify ( payload ) ) ;
171+
172+ const dataToSignString = `${ headerEncoded } .${ payloadEncoded } ` ;
173+
174+ const encoder = new TextEncoder ( ) ;
175+ const secretKeyData = encoder . encode ( secret ) ; // Secret is a string, convert to Uint8Array
176+ const dataToSign = encoder . encode ( dataToSignString ) ;
177+
178+ const cryptoKey = await crypto . subtle . importKey (
179+ "raw" , // format: raw key data
180+ secretKeyData , // keyData: Uint8Array of the secret
181+ { name : "HMAC" , hash : "SHA-256" } , // algorithm details
182+ false , // extractable: whether the key can be exported
183+ [ "sign" ] // keyUsages: "sign" for HMAC
184+ ) ;
185+
186+ const signatureBuffer = await crypto . subtle . sign (
187+ "HMAC" , // algorithm name
188+ cryptoKey , // CryptoKey for signing
189+ dataToSign // Data to sign as ArrayBuffer or TypedArray
190+ ) ;
191+
192+ const signatureEncoded = arrayBufferToBase64Url ( signatureBuffer ) ;
193+
194+ return `${ dataToSignString } .${ signatureEncoded } ` ;
195+
196+ function stringToBase64Url ( str ) {
197+ const encoder = new TextEncoder ( ) ;
198+ const uint8Array = encoder . encode ( str ) ;
199+ let binaryString = '' ;
200+ uint8Array . forEach ( byte => {
201+ binaryString += String . fromCharCode ( byte ) ;
202+ } ) ;
203+ return btoa ( binaryString )
204+ . replace ( / \+ / g, '-' )
205+ . replace ( / \/ / g, '_' )
206+ . replace ( / = + $ / , '' ) ;
207+ }
208+
209+ // Helper function to Base64URL encode an ArrayBuffer
210+ function arrayBufferToBase64Url ( buffer ) {
211+ let binary = '' ;
212+ const bytes = new Uint8Array ( buffer ) ;
213+ const len = bytes . byteLength ;
214+ for ( let i = 0 ; i < len ; i ++ ) {
215+ binary += String . fromCharCode ( bytes [ i ] ) ;
216+ }
217+ return btoa ( binary )
218+ . replace ( / \+ / g, '-' )
219+ . replace ( / \/ / g, '_' )
220+ . replace ( / = + $ / , '' ) ;
221+ }
222+ }
0 commit comments