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