@@ -399,6 +399,36 @@ describe('maskOnyxState', () => {
399399 expect ( result . lastMessageHtml ) . not . toBe ( input . lastMessageHtml ) ;
400400 }
401401 } ) ;
402+
403+ it . each ( [
404+ [ 'masking enabled' , true ] ,
405+ [ 'masking disabled' , false ] ,
406+ ] ) ( 'should mask delegate credentials held under hybridApp with %s' , ( _label , isMaskingEnabled ) => {
407+ // Given a hybridApp key holding live OldDot delegate credentials in delegateAccessData
408+ const credentialValues = [ 'live-olddot-auth-token' , 'live-olddot-encrypted-token' , 'auto-generated-login' , 'auto-generated-password' , 'delegate@example.com' ] ;
409+ const input = {
410+ session : mockSession ,
411+ [ ONYXKEYS . HYBRID_APP ] : {
412+ isSingleNewDotEntry : true ,
413+ delegateAccessData : {
414+ isDelegateAccess : true ,
415+ oldDotCurrentAuthToken : 'live-olddot-auth-token' ,
416+ oldDotCurrentEncryptedAuthToken : 'live-olddot-encrypted-token' ,
417+ oldDotAutoGeneratedLogin : 'auto-generated-login' ,
418+ oldDotAutoGeneratedPassword : 'auto-generated-password' ,
419+ oldDotCurrentUserEmail : 'delegate@example.com' ,
420+ } ,
421+ } ,
422+ } ;
423+
424+ // When the state is exported (the export rule applies regardless of the masking toggle)
425+ const serialized = JSON . stringify ( maskOnyxState ( input , isMaskingEnabled ) ) ;
426+
427+ // Then none of the credential values may appear verbatim anywhere in the export
428+ for ( const credentialValue of credentialValues ) {
429+ expect ( serialized ) . not . toContain ( credentialValue ) ;
430+ }
431+ } ) ;
402432} ) ;
403433
404434// These tests check that every Onyx key is sorted into a bucket and that no key lands in two of them.
@@ -457,6 +487,7 @@ describe('Onyx key export coverage', () => {
457487 ONYXKEYS . ACCOUNT ,
458488 ONYXKEYS . PRESERVED_USER_SESSION ,
459489 ONYXKEYS . PRESERVED_ACCOUNT ,
490+ ONYXKEYS . HYBRID_APP ,
460491 ONYXKEYS . PERSONAL_DETAILS_LIST ,
461492 ONYXKEYS . PRIVATE_PERSONAL_DETAILS ,
462493 ONYXKEYS . LOGINS ,
@@ -488,6 +519,17 @@ describe('Onyx key export coverage', () => {
488519 }
489520 } ) ;
490521
522+ it ( 'session token field names must be in keysToMask so the maskFragileData fallback can never pass them through' , ( ) => {
523+ // maskFragileData exports any field name it does not recognize verbatim. Keys that reach it and hold a
524+ // session-token field rely on keysToMask to catch the secret, so these generic field names must stay
525+ // listed as a backstop even though the keys that carry them today have their own export rules.
526+ const credentialFieldNames = [ 'authToken' , 'encryptedAuthToken' , 'supportAuthToken' ] ;
527+
528+ for ( const fieldName of credentialFieldNames ) {
529+ expect ( keysToMask . has ( fieldName ) ) . toBe ( true ) ;
530+ }
531+ } ) ;
532+
491533 it ( 'no key should appear in multiple buckets' , ( ) => {
492534 const rulesKeys = Object . keys ( ONYX_KEY_EXPORT_RULES ) ;
493535 const removeKeys = new Set < string > ( Array . from ( onyxKeysToRemove ) . filter ( ( key ) : key is Extract < typeof key , string > => typeof key === 'string' ) ) ;
0 commit comments