The app shows a 3-second countdown popup when the Start button is tapped. That is the only user-visible feature: there is no map, no search, and no other location-based functionality anywhere in the UI.
Despite that, the app requests location access and collects coarse location coordinates (kilometer-level accuracy) in the background for the full duration of the countdown. When the countdown ends, the coordinates are written to location_capture.txt in the app's Documents directory. The user sees a "3s Timer started" popup with no mention of location at any point.
The Info.plist declares a single purpose string, NSLocationWhenInUseUsageDescription, with the text "We use your location to show you nearby content and recommendations." This is deceptive: it describes a feature that does not exist in the app, and the purpose string shown in the system prompt is inconsistent with the only observable behavior (a countdown).
// SUMMARY: An app that shows a 3-second countdown popup when Start is tapped. The only// user-visible feature is the countdown; there is no map, search, or any other// location-based feature in the UI. Despite that, the app silently requests location access// and records the GPS coordinates to a file while the countdown runs. The// NSLocationWhenInUseUsageDescription purpose string is deceptive: it claims a// "nearby content" feature that the app does not provide.importUIKitimportCoreLocationstructMastgTest{staticvarlocationCapture:LocationCapture?staticfuncmastgTest(completion:@escaping(String)->Void){// FAIL: [MASTG-TEST-0361] The app requests location access and collects GPS// coordinates while showing a plain countdown popup. The// NSLocationWhenInUseUsageDescription purpose string ("to show you nearby content// and recommendations") is deceptive: the app has no location-based feature and// never shows any content related to the user's location.locationCapture=LocationCapture()locationCapture?.onAuthorized={// Start the countdown only after permission is granted so GPS collection// is running for the full 3 seconds.presentCountdown(seconds:3){locationCapture?.stop()letfileURL=saveLocation(locationCapture?.capturedLocation)completion(buildResult(fileURL:fileURL,location:locationCapture?.capturedLocation))locationCapture=nil}}locationCapture?.onDenied={letdir=FileManager.default.urls(for:.documentDirectory,in:.userDomainMask)[0]leturl=dir.appendingPathComponent("location_capture.txt")try?"Location access denied.".write(to:url,atomically:true,encoding:.utf8)completion("Location access denied.\n\nSaved to: location_capture.txt")locationCapture=nil}}// MARK: - PrivateprivatestaticfuncpresentCountdown(seconds:Int,onFinish:@escaping()->Void){DispatchQueue.main.async{guardletscene=UIApplication.shared.connectedScenes.firstas?UIWindowScene,letwindow=scene.windows.first(where:\.isKeyWindow),letrootVC=window.rootViewControllerelse{onFinish();return}vartopVC=rootVCwhileletp=topVC.presentedViewController{topVC=p}letalert=UIAlertController(title:"3s Timer started",message:"Please wait… \(seconds)s",preferredStyle:.alert)topVC.present(alert,animated:true)varremaining=secondslettimer=Timer.scheduledTimer(withTimeInterval:1.0,repeats:true){tinremaining-=1ifremaining>0{alert.message="Please wait… \(remaining)s"}else{t.invalidate()alert.dismiss(animated:true,completion:onFinish)}}RunLoop.main.add(timer,forMode:.common)}}privatestaticfuncsaveLocation(_location:CLLocation?)->URL{letdir=FileManager.default.urls(for:.documentDirectory,in:.userDomainMask)[0]leturl=dir.appendingPathComponent("location_capture.txt")letcontent:Stringifletloc=location{letfmt=ISO8601DateFormatter()content=""" Latitude: \(loc.coordinate.latitude) Longitude: \(loc.coordinate.longitude) Altitude: \(String(format:"%.1f",loc.altitude)) m Accuracy: \(String(format:"%.1f",loc.horizontalAccuracy)) m Timestamp: \(fmt.string(from:loc.timestamp)) """}else{content="Location not available (no fix received)."}try?content.write(to:url,atomically:true,encoding:.utf8)returnurl}privatestaticfuncbuildResult(fileURL:URL,location:CLLocation?)->String{ifletloc=location{return""" Countdown completed. [!] Location captured in the background: Latitude: \(loc.coordinate.latitude) Longitude: \(loc.coordinate.longitude) Altitude: \(String(format:"%.1f",loc.altitude)) m Saved to: \(fileURL.lastPathComponent) """}else{return""" Countdown completed. [!] Location capture attempted (no GPS fix received in time). Saved to: \(fileURL.lastPathComponent) """}}}// MARK: - Location capturefinalclassLocationCapture:NSObject,CLLocationManagerDelegate{privateletmanager=CLLocationManager()private(set)varcapturedLocation:CLLocation?varonAuthorized:(()->Void)?varonDenied:(()->Void)?overrideinit(){super.init()manager.delegate=selfmanager.desiredAccuracy=kCLLocationAccuracyKilometermanager.requestWhenInUseAuthorization()}funcstop(){manager.stopUpdatingLocation()}funclocationManagerDidChangeAuthorization(_manager:CLLocationManager){switchmanager.authorizationStatus{case.authorizedWhenInUse,.authorizedAlways:manager.startUpdatingLocation()onAuthorized?()case.denied,.restricted:onDenied?()default:break}}funclocationManager(_manager:CLLocationManager,didUpdateLocationslocations:[CLLocation]){capturedLocation=locations.last}}
12345678
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to show you nearby content and recommendations.</string>
</dict>
</plist>
// Hook the location authorization and collection APIs at runtime.// This script traces (1) the permission request that displays the// NSLocationWhenInUseUsageDescription purpose string, (2) the start of GPS collection,// and (3) the stop, which marks the end of the capture window.console.log("\n[*] Starting Location Access Tracing...\n");constprintBacktrace=(context,maxLines=8)=>{console.log("\nBacktrace:");letbacktrace=Thread.backtrace(context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress);for(leti=0;i<Math.min(maxLines,backtrace.length);i++){console.log(backtrace[i]);}};if(ObjC.available){// CLLocationManager.requestWhenInUseAuthorization (instance method).// Displays NSLocationWhenInUseUsageDescription to the user when status is notDetermined.try{Interceptor.attach(ObjC.classes.CLLocationManager["- requestWhenInUseAuthorization"].implementation,{onEnter(args){console.log("\n[+] CLLocationManager.requestWhenInUseAuthorization called");console.log(" Purpose string key: NSLocationWhenInUseUsageDescription");printBacktrace(this.context);}});console.log("[*] Hooked CLLocationManager.requestWhenInUseAuthorization");}catch(e){console.log("[-] Failed to hook requestWhenInUseAuthorization: "+e);}// CLLocationManager.startUpdatingLocation (instance method).// Reaching this confirms the app actively collects GPS coordinates.try{Interceptor.attach(ObjC.classes.CLLocationManager["- startUpdatingLocation"].implementation,{onEnter(args){console.log("\n[+] CLLocationManager.startUpdatingLocation called");console.log(" GPS coordinate collection has started.");printBacktrace(this.context);}});console.log("[*] Hooked CLLocationManager.startUpdatingLocation");}catch(e){console.log("[-] Failed to hook startUpdatingLocation: "+e);}// CLLocationManager.stopUpdatingLocation (instance method).// Marks the end of the capture window (called when the countdown ends).try{Interceptor.attach(ObjC.classes.CLLocationManager["- stopUpdatingLocation"].implementation,{onEnter(args){console.log("\n[+] CLLocationManager.stopUpdatingLocation called");console.log(" GPS coordinate collection has stopped.");printBacktrace(this.context);}});console.log("[*] Hooked CLLocationManager.stopUpdatingLocation");}catch(e){console.log("[-] Failed to hook stopUpdatingLocation: "+e);}}console.log("\n[*] Hooks installed. Tap the Start button to trigger the countdown and location capture.\n");
[*]StartingLocationAccessTracing...[*]HookedCLLocationManager.requestWhenInUseAuthorization[*]HookedCLLocationManager.startUpdatingLocation[*]HookedCLLocationManager.stopUpdatingLocation[*]Hooksinstalled.TaptheStartbuttontotriggerthecountdownandlocationcapture.[+]CLLocationManager.requestWhenInUseAuthorizationcalledPurposestringkey:NSLocationWhenInUseUsageDescriptionBacktrace:0x1010cfef0MASTestApp.debug.dylib!LocationCapture.init()0x1010cff38MASTestApp.debug.dylib!@objcLocationCapture.init()0x1010cc534MASTestApp.debug.dylib!LocationCapture.__allocating_init()0x1010cc2a0MASTestApp.debug.dylib!staticMastgTest.mastgTest(completion:)0x1010d26e0MASTestApp.debug.dylib!closure#1 in closure #1 in closure #1 in ContentView.body.getter0x1920fd550SwiftUI!0x150c550(0x18cec1550)0x191994c30SwiftUI!0xda3c30(0x18c758c30)0x1919986bcSwiftUI!0xda76bc(0x18c75c6bc)[+]CLLocationManager.startUpdatingLocationcalledGPScoordinatecollectionhasstarted.Backtrace:0x1010cffd4MASTestApp.debug.dylib!LocationCapture.locationManagerDidChangeAuthorization(_:)0x1010d0134MASTestApp.debug.dylib!@objcLocationCapture.locationManagerDidChangeAuthorization(_:)0x199549cccCoreLocation!-[CLLocationManageronClientEventAuthStatus:]0x199543b74CoreLocation!-[CLLocationManageronClientEvent:supportInfo:]0x1995438e4CoreLocation!0x108e4(0x1943078e4)0x1aa8e0524LocationSupport!-[CLSiloprepareAndRunBlock:]0x18d0fe9b8CoreFoundation!__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__0x18d0ed910CoreFoundation!__CFRunLoopDoBlocks[+]CLLocationManager.stopUpdatingLocationcalledGPScoordinatecollectionhasstopped.Backtrace:0x1010cca18MASTestApp.debug.dylib!LocationCapture.stop()0x1010cc6b4MASTestApp.debug.dylib!closure#1 in closure #1 in static MastgTest.mastgTest(completion:)0x1010cef18MASTestApp.debug.dylib!thunkfor@escaping@callee_guaranteed()->()0x18f4a53a0UIKitCore!-[UIPresentationControllertransitionDidFinish:]0x18f9da39cUIKitCore!__56-[UIPresentationControllerrunTransitionForCurrentState]_block_invoke.910x18f4f950cUIKitCore!-[_UIViewControllerTransitionContextcompleteTransition:]0x18f29af94UIKitCore!__UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__0x18f29ae9cUIKitCore!-[UIViewAnimationBlockDelegate_didEndBlockAnimation:finished:context:]
The runtime trace shows that tapping Start:
Calls CLLocationManager.requestWhenInUseAuthorization, which displays NSLocationWhenInUseUsageDescription to the user. The backtrace links the call to LocationCapture.init(), invoked via LocationCapture.__allocating_init() from MastgTest.mastgTest(completion:).
Calls CLLocationManager.startUpdatingLocation once the user grants permission. The backtrace originates in LocationCapture.locationManagerDidChangeAuthorization(_:), called by CoreLocation after authorization is granted, confirming that coarse location collection begins immediately.
Calls CLLocationManager.stopUpdatingLocation when the countdown ends. The backtrace shows LocationCapture.stop() called from closure #1 in closure #1 in static MastgTest.mastgTest(completion:), which is the onFinish block invoked by UIKit (-[UIPresentationController transitionDidFinish:]) when the countdown alert's dismiss animation completes.
The test case fails because the declared purpose string is deceptive. It tells the user that location is used to show nearby content and recommendations, but the app does not provide any nearby content, recommendations, map, search, or other location-based feature.
The only user-visible feature is a 3-second countdown popup. The runtime trace confirms that location authorization and collection APIs are reached during that countdown flow, so the purpose string is not merely unused or stale. It is shown for reachable location access that is inconsistent with the app's observable behavior.
The user must still grant location permission before the app can access the protected resource. However, the issue remains valid because the authorization prompt is based on an inaccurate explanation, and the observed location access is not justified by the app's visible functionality.