Skip to content

isAppStore always returns true on macOS Release builds (TARGET_OS_OSX not recognized by Swift #if) #280

@vmurin

Description

@vmurin

SDK Version

  • Affected: SwiftClient 2.11.0 (confirmed) and 2.12.0 (code-reviewed)
  • File: Sources/TelemetryDeck/Signals/Signal+Helpers.swift, lines 121-131

Summary

On macOS, the isAppStore property incorrectly returns true for all non-Debug Release builds (including Developer ID signed and unsigned local builds). This causes the TelemetryDeck.RunContext.isAppStore and legacy isAppStore signal parameters to report incorrect values for macOS apps.

Root Cause

The isAppStore computed property uses C preprocessor macros in Swift #if directives:

static var isAppStore: Bool {
    #if DEBUG
        return false
    #elseif TARGET_OS_OSX || TARGET_OS_MACCATALYST    // <-- BUG
        return false
    #elseif targetEnvironment(simulator)
        return false
    #else
        return !isSimulatorOrTestFlight
    #endif
}

TARGET_OS_OSX and TARGET_OS_MACCATALYST are C preprocessor macros (defined in <TargetConditionals.h>). Swift's #if directive does not recognize C macros — they are always evaluated as false. The correct Swift compilation conditions are:

  • os(macOS) instead of TARGET_OS_OSX
  • targetEnvironment(macCatalyst) instead of TARGET_OS_MACCATALYST

Because the #elseif TARGET_OS_OSX branch is silently skipped, the code falls through to #else, which returns !isSimulatorOrTestFlight.

Why isSimulatorOrTestFlight returns false on macOS dev builds

The isSimulatorOrTestFlight property (lines 107-113) checks for a sandbox receipt:

static var isSimulatorOrTestFlight: Bool {
    guard !isDebug, !isSimulator else { return false }
    guard let receiptURL = Bundle.main.appStoreReceiptURL else { return false }
    return receiptURL.lastPathComponent == "sandboxReceipt" || receiptURL.path.contains("sandboxReceipt")
}

A macOS Release build from Xcode (not Debug, not TestFlight) has no sandbox receipt, so this returns false. Therefore isAppStore = !false = true.

Affected Configurations

Build type #if DEBUG TARGET_OS_OSX (Swift) Falls to #else isSimulatorOrTestFlight isAppStore result Expected
macOS Debug true N/A (short-circuits) no N/A false false
macOS Release (dev/local) false false (C macro, ignored) yes false (no receipt) true false
macOS Release (Developer ID) false false (C macro, ignored) yes false (no receipt) true false
macOS Release (App Store) false false (C macro, ignored) yes false (has real receipt, not sandbox) true true
macOS Release (TestFlight) false false (C macro, ignored) yes true (sandbox receipt) false false

Only the App Store and TestFlight rows produce correct results — and only by accident (the receipt-based check happens to give the right answer). Dev and Developer ID builds are wrong.

Suggested Fix

Replace C macros with Swift compilation conditions:

static var isAppStore: Bool {
    #if DEBUG
        return false
    #elseif os(macOS) || targetEnvironment(macCatalyst)
        return false
    #elseif targetEnvironment(simulator)
        return false
    #else
        return !isSimulatorOrTestFlight
    #endif
}

Note: This fix makes isAppStore always return false on macOS (matching the original intent). If TelemetryDeck wants to properly detect macOS App Store builds, a separate receipt-based or provisioning-profile-based check would be needed, similar to what isSimulatorOrTestFlight does for iOS TestFlight.

Impact

Any macOS app using TelemetryDeck SwiftClient/SwiftSDK 2.x in Release configuration (non-Debug) will incorrectly report isAppStore = true in their signal parameters (TelemetryDeck.RunContext.isAppStore and legacy isAppStore). This affects dashboard filtering and segmentation for all macOS TelemetryDeck users.

Reproduction

  1. Create a macOS app using TelemetryDeck SwiftSDK 2.11.0 or 2.12.0
  2. Build with Release configuration (not Debug)
  3. Send any TelemetryDeck signal
  4. Observe isAppStore parameter is true in the dashboard, even though the app was not distributed via the App Store

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions