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