@@ -19,6 +19,7 @@ use async_trait::async_trait;
1919use error_stack:: { Report , ResultExt } ;
2020use fastly:: http:: { header, Method , StatusCode } ;
2121use fastly:: { Request , Response } ;
22+ use regex:: Regex ;
2223use serde:: Deserialize ;
2324use validator:: Validate ;
2425
@@ -29,7 +30,6 @@ use crate::integrations::{
2930 IntegrationEndpoint , IntegrationProxy , IntegrationRegistration ,
3031} ;
3132use crate :: settings:: { IntegrationConfig as IntegrationConfigTrait , Settings } ;
32- use crate :: streaming_replacer:: StreamingReplacer ;
3333
3434const LOCKR_INTEGRATION_ID : & str = "lockr" ;
3535
@@ -103,21 +103,24 @@ impl LockrIntegration {
103103 /// Rewrite the host variable in the Lockr SDK JavaScript.
104104 ///
105105 /// Replaces the obfuscated host assignment with a direct assignment to the
106- /// first-party API proxy endpoint.
106+ /// first-party API proxy endpoint. Uses regex to match varying obfuscation patterns.
107107 fn rewrite_sdk_host ( & self , sdk_body : Vec < u8 > ) -> Result < Vec < u8 > , Report < TrustedServerError > > {
108- // Pattern to find: 'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e)
109- // This is the obfuscated way Lockr sets the API host
110- // We'll replace it with a relative URL to use the first-party proxy
111-
112- let mut replacer = StreamingReplacer :: new_single (
113- "'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e)" ,
114- "'host': '/integrations/lockr/api'" ,
115- ) ;
116-
117- // Process the entire SDK in one chunk
118- let processed = replacer. process_chunk ( & sdk_body, true ) ;
119-
120- Ok ( processed)
108+ // Convert bytes to string
109+ let sdk_string = String :: from_utf8 ( sdk_body)
110+ . change_context ( Self :: error ( "SDK content is not valid UTF-8" ) ) ?;
111+
112+ // Pattern matches: 'host': _0xABCDEF(0x123) + _0xABCDEF(0x456) + _0xABCDEF(0x789)
113+ // This is the obfuscated way Lockr constructs the API host
114+ // The function names and hex values change with each build, so we use regex
115+ let pattern = Regex :: new (
116+ r"'host':\s*_0x[a-f0-9]+\(0x[a-f0-9]+\)\s*\+\s*_0x[a-f0-9]+\(0x[a-f0-9]+\)\s*\+\s*_0x[a-f0-9]+\(0x[a-f0-9]+\)" ,
117+ )
118+ . change_context ( Self :: error ( "Failed to compile regex pattern" ) ) ?;
119+
120+ // Replace with first-party API proxy endpoint
121+ let rewritten = pattern. replace ( & sdk_string, "'host': '/integrations/lockr/api'" ) ;
122+
123+ Ok ( rewritten. as_bytes ( ) . to_vec ( ) )
121124 }
122125
123126 /// Handle SDK serving - fetch from Lockr CDN and serve through first-party domain.
@@ -551,8 +554,8 @@ mod tests {
551554 } ;
552555 let integration = LockrIntegration :: new ( config) ;
553556
554- // Mock obfuscated SDK JavaScript with the host pattern
555- let mock_sdk = r#"
557+ // Mock obfuscated SDK JavaScript with the host pattern (old pattern)
558+ let mock_sdk_old = r#"
556559const identityLockr = {
557560 'host': _0x3a740e(0x3d1) + _0x3a740e(0x367) + _0x3a740e(0x14e),
558561 'app_id': null,
@@ -562,7 +565,7 @@ const identityLockr = {
562565};
563566 "# ;
564567
565- let result = integration. rewrite_sdk_host ( mock_sdk . as_bytes ( ) . to_vec ( ) ) ;
568+ let result = integration. rewrite_sdk_host ( mock_sdk_old . as_bytes ( ) . to_vec ( ) ) ;
566569 assert ! ( result. is_ok( ) ) ;
567570
568571 let rewritten = String :: from_utf8 ( result. unwrap ( ) ) . unwrap ( ) ;
@@ -578,6 +581,48 @@ const identityLockr = {
578581 assert ! ( rewritten. contains( "'firstPartyCookies': []" ) ) ;
579582 }
580583
584+ #[ test]
585+ fn test_sdk_host_rewriting_real_pattern ( ) {
586+ let config = LockrConfig {
587+ enabled : true ,
588+ app_id : "test-app-id" . to_string ( ) ,
589+ api_endpoint : default_api_endpoint ( ) ,
590+ sdk_url : default_sdk_url ( ) ,
591+ cache_ttl_seconds : 3600 ,
592+ rewrite_sdk : true ,
593+ rewrite_sdk_host : true ,
594+ } ;
595+ let integration = LockrIntegration :: new ( config) ;
596+
597+ // Real obfuscated SDK JavaScript from actual Lockr SDK
598+ let mock_sdk_real = r#"
599+ const identityLockr = {
600+ 'host': _0x4ed951(0xcb) + _0x4ed951(0x173) + _0x4ed951(0x1c2),
601+ 'app_id': null,
602+ 'expiryDateKeys': localStorage['getItem']('identityLockr_expiryDateKeys') ? JSON['parse'](localStorage['getItem']('identityLockr_expiryDateKeys')) : [],
603+ 'firstPartyCookies': [],
604+ 'canRefreshToken': !![]
605+ };
606+ "# ;
607+
608+ let result = integration. rewrite_sdk_host ( mock_sdk_real. as_bytes ( ) . to_vec ( ) ) ;
609+ assert ! ( result. is_ok( ) ) ;
610+
611+ let rewritten = String :: from_utf8 ( result. unwrap ( ) ) . unwrap ( ) ;
612+
613+ // Verify the host was rewritten to the proxy endpoint
614+ assert ! ( rewritten. contains( "'host': '/integrations/lockr/api'" ) ) ;
615+
616+ // Verify the obfuscated pattern was removed
617+ assert ! ( !rewritten. contains( "_0x4ed951(0xcb)" ) ) ;
618+ assert ! ( !rewritten. contains( "_0x4ed951(0x173)" ) ) ;
619+ assert ! ( !rewritten. contains( "_0x4ed951(0x1c2)" ) ) ;
620+
621+ // Verify other parts of the code remain intact
622+ assert ! ( rewritten. contains( "'app_id': null" ) ) ;
623+ assert ! ( rewritten. contains( "'firstPartyCookies': []" ) ) ;
624+ }
625+
581626 #[ test]
582627 fn test_sdk_host_rewriting_disabled ( ) {
583628 let config = LockrConfig {
0 commit comments