Skip to content

Commit 5fce94f

Browse files
authored
fix: Disable SessionSentryReplayIntegration if the environment is unsafe (#6573)
* fix: Disable SessionSentryReplayIntegration if the environment is unsafe * Simplify shouldEnableSessionReplay * Rename test * Add log message * Update changelog * Safely unwrap SentryOptions
1 parent e537c90 commit 5fce94f

File tree

9 files changed

+228
-89
lines changed

9 files changed

+228
-89
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

7+
- Disable SessionSentryReplayIntegration if the environment is unsafe [#6573]
78
- Fix crash when last replay info is missing some keys [#6577]
89

910
## 8.57.0

Sentry.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@
10121012
F41362112E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */; };
10131013
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */; };
10141014
F41362152E1C568400B84443 /* SentryScopePersistentStore+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */; };
1015+
F426748D2EB11E7900E09150 /* SentryReplayApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */; };
10151016
F443DB272E09BE8C009A9045 /* LoadValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */; };
10161017
F44858132E03579D0013E63B /* SentryCrashDynamicLinker+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */; };
10171018
F451FAA62E0B304E0050ACF2 /* LoadValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */; };
@@ -2376,6 +2377,7 @@
23762377
F41362102E1C55AF00B84443 /* SentryScopePersistentStore+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Tags.swift"; sourceTree = "<group>"; };
23772378
F41362122E1C566100B84443 /* SentryScopePersistentStore+User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+User.swift"; sourceTree = "<group>"; };
23782379
F41362142E1C568400B84443 /* SentryScopePersistentStore+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryScopePersistentStore+Context.swift"; sourceTree = "<group>"; };
2380+
F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryReplayApiTests.swift; sourceTree = "<group>"; };
23792381
F443DB262E09BE8C009A9045 /* LoadValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadValidatorTests.swift; sourceTree = "<group>"; };
23802382
F44858122E0357940013E63B /* SentryCrashDynamicLinker+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashDynamicLinker+Test.h"; sourceTree = "<group>"; };
23812383
F451FAA52E0B304E0050ACF2 /* LoadValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadValidator.swift; sourceTree = "<group>"; };
@@ -4418,6 +4420,7 @@
44184420
D80694C12B7CC85800B820E6 /* SessionReplay */ = {
44194421
isa = PBXGroup;
44204422
children = (
4423+
F42674872EB11E7000E09150 /* SentryReplayApiTests.swift */,
44214424
D4D0E1E22E9D040800358814 /* SentrySessionReplayEnvironmentCheckerTests.swift */,
44224425
D49480D22DC23E8E00A3B6E9 /* SentryReplayTypeTests.swift */,
44234426
D80694C22B7CC86E00B820E6 /* SentryReplayEventTests.swift */,
@@ -6371,6 +6374,7 @@
63716374
D4CA34832E378C9900E92A61 /* SentryArrayTests.swift in Sources */,
63726375
7B05A61824A4D14A00EF211D /* SentrySessionGeneratorTests.swift in Sources */,
63736376
D8CB742B294B1DD000A5F964 /* SentryUIApplicationTests.swift in Sources */,
6377+
F426748D2EB11E7900E09150 /* SentryReplayApiTests.swift in Sources */,
63746378
63FE720920DA66EC00CDBAE8 /* XCTestCase+SentryCrash.m in Sources */,
63756379
D8918B222849FA6D00701F9A /* SentrySDKIntegrationTestsBase.swift in Sources */,
63766380
620078782D3906BF0022CB67 /* SentryCodableTests.swift in Sources */,

Sources/Sentry/SentryReplayApi.m

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# import "SentryHub+Private.h"
66
# import "SentryInternalCDefines.h"
7+
# import "SentryInternalDefines.h"
78
# import "SentryLogC.h"
89
# import "SentryOptions+Private.h"
910
# import "SentrySDK+Private.h"
@@ -55,9 +56,16 @@ - (void)start SENTRY_DISABLE_THREAD_SANITIZER("double-checked lock produce false
5556
@synchronized(self) {
5657
replayIntegration = (SentrySessionReplayIntegration *)[SentrySDKInternal.currentHub
5758
getInstalledIntegration:SentrySessionReplayIntegration.class];
58-
if (replayIntegration == nil) {
59+
if (replayIntegration == nil && SentrySDKInternal.currentHub.client.options) {
60+
SentryOptions *currentOptions = SENTRY_UNWRAP_NULLABLE(
61+
SentryOptions, SentrySDKInternal.currentHub.client.options);
62+
if (![SentrySessionReplayIntegration shouldEnableForOptions:currentOptions]) {
63+
SENTRY_LOG_ERROR(@"[Session Replay] Session replay is disabled due to "
64+
@"environment potentially causing PII leaks.");
65+
return;
66+
}
5967
SENTRY_LOG_DEBUG(@"[Session Replay] Initializing replay integration");
60-
SentryOptions *currentOptions = SentrySDKInternal.currentHub.client.options;
68+
6169
replayIntegration =
6270
[[SentrySessionReplayIntegration alloc] initForManualUse:currentOptions];
6371

Sources/Sentry/SentrySessionReplayIntegration.m

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ - (void)newSceneActivate;
5353
@implementation SentrySessionReplayIntegration {
5454
BOOL _startedAsFullSession;
5555
SentryReplayOptions *_replayOptions;
56-
SentryExperimentalOptions *_experimentalOptions;
5756
id<SentryNSNotificationCenterWrapper> _notificationCenter;
5857
id<SentryRateLimits> _rateLimits;
5958
id<SentryViewScreenshotProvider> _currentScreenshotProvider;
@@ -65,7 +64,14 @@ @implementation SentrySessionReplayIntegration {
6564
// replay absolutely needs segment 0 to make replay work.
6665
BOOL _rateLimited;
6766
id<SentryCurrentDateProvider> _dateProvider;
68-
id<SentrySessionReplayEnvironmentCheckerProvider> _environmentChecker;
67+
}
68+
69+
+ (BOOL)shouldEnableForOptions:(SentryOptions *)options
70+
{
71+
return [SentrySessionReplay
72+
shouldEnableSessionReplayWithEnvironmentChecker:SentryDependencyContainer.sharedInstance
73+
.sessionReplayEnvironmentChecker
74+
experimentalOptions:options.experimental];
6975
}
7076

7177
- (instancetype)init
@@ -78,39 +84,34 @@ - (instancetype)initForManualUse:(nonnull SentryOptions *)options
7884
{
7985
if (self = [super init]) {
8086
[self setupWith:options.sessionReplay
81-
experimentalOptions:options.experimental
8287
enableTouchTracker:options.enableSwizzling
8388
enableViewRendererV2:options.sessionReplay.enableViewRendererV2
8489
enableFastViewRendering:options.sessionReplay.enableFastViewRendering];
85-
[self startWithOptions:options.sessionReplay
86-
experimentalOptions:options.experimental
87-
fullSession:YES];
90+
[self startWithOptions:options.sessionReplay fullSession:YES];
8891
}
8992
return self;
9093
}
9194

9295
- (BOOL)installWithOptions:(nonnull SentryOptions *)options
9396
{
94-
if ([super installWithOptions:options] == NO) {
97+
if ([super installWithOptions:options] == NO ||
98+
[SentrySessionReplayIntegration shouldEnableForOptions:options] == NO) {
9599
return NO;
96100
}
97101

98102
[self setupWith:options.sessionReplay
99-
experimentalOptions:options.experimental
100103
enableTouchTracker:options.enableSwizzling
101104
enableViewRendererV2:options.sessionReplay.enableViewRendererV2
102105
enableFastViewRendering:options.sessionReplay.enableFastViewRendering];
103106
return YES;
104107
}
105108

106109
- (void)setupWith:(SentryReplayOptions *)replayOptions
107-
experimentalOptions:(SentryExperimentalOptions *)experimentalOptions
108110
enableTouchTracker:(BOOL)touchTracker
109111
enableViewRendererV2:(BOOL)enableViewRendererV2
110112
enableFastViewRendering:(BOOL)enableFastViewRendering
111113
{
112114
_replayOptions = replayOptions;
113-
_experimentalOptions = experimentalOptions;
114115
_rateLimits = SentryDependencyContainer.sharedInstance.rateLimits;
115116
_dateProvider = SentryDependencyContainer.sharedInstance.dateProvider;
116117

@@ -141,7 +142,6 @@ - (void)setupWith:(SentryReplayOptions *)replayOptions
141142

142143
_notificationCenter = SentryDependencyContainer.sharedInstance.notificationCenterWrapper;
143144
_dateProvider = SentryDependencyContainer.sharedInstance.dateProvider;
144-
_environmentChecker = SentryDependencyContainer.sharedInstance.sessionReplayEnvironmentChecker;
145145

146146
// We use the dispatch queue provider as a factory to create the queues, but store the queues
147147
// directly in this instance, so they get deallocated when the integration is deallocated.
@@ -354,9 +354,7 @@ - (void)runReplayForAvailableWindow
354354
if ([SentryDependencyContainer.sharedInstance.application getWindows].count > 0) {
355355
SENTRY_LOG_DEBUG(@"[Session Replay] Running replay for available window");
356356
// If a window its already available start replay right away
357-
[self startWithOptions:_replayOptions
358-
experimentalOptions:_experimentalOptions
359-
fullSession:_startedAsFullSession];
357+
[self startWithOptions:_replayOptions fullSession:_startedAsFullSession];
360358
} else if (@available(iOS 13.0, tvOS 13.0, *)) {
361359
SENTRY_LOG_DEBUG(
362360
@"[Session Replay] Waiting for a scene to be available to started the replay");
@@ -376,27 +374,22 @@ - (void)newSceneActivate
376374
removeObserver:self
377375
name:UISceneDidActivateNotification
378376
object:nil];
379-
[self startWithOptions:_replayOptions
380-
experimentalOptions:_experimentalOptions
381-
fullSession:_startedAsFullSession];
377+
[self startWithOptions:_replayOptions fullSession:_startedAsFullSession];
382378
}
383379
}
384380

385381
- (void)startWithOptions:(SentryReplayOptions *)replayOptions
386-
experimentalOptions:(SentryExperimentalOptions *)experimentalOptions
387382
fullSession:(BOOL)shouldReplayFullSession
388383
{
389384
SENTRY_LOG_DEBUG(@"[Session Replay] Starting session");
390385
[self startWithOptions:replayOptions
391-
experimentalOptions:experimentalOptions
392386
screenshotProvider:_currentScreenshotProvider ?: _viewPhotographer
393387
breadcrumbConverter:_currentBreadcrumbConverter
394388
?: [[SentrySRDefaultBreadcrumbConverter alloc] init]
395389
fullSession:shouldReplayFullSession];
396390
}
397391

398392
- (void)startWithOptions:(SentryReplayOptions *)replayOptions
399-
experimentalOptions:(SentryExperimentalOptions *)experimentalOptions
400393
screenshotProvider:(id<SentryViewScreenshotProvider>)screenshotProvider
401394
breadcrumbConverter:(id<SentryReplayBreadcrumbConverter>)breadcrumbConverter
402395
fullSession:(BOOL)shouldReplayFullSession
@@ -431,16 +424,14 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions
431424

432425
SentryDisplayLinkWrapper *displayLinkWrapper = [[SentryDisplayLinkWrapper alloc] init];
433426
self.sessionReplay = [[SentrySessionReplay alloc] initWithReplayOptions:replayOptions
434-
experimentalOptions:experimentalOptions
435427
replayFolderPath:docs
436428
screenshotProvider:screenshotProvider
437429
replayMaker:replayMaker
438430
breadcrumbConverter:breadcrumbConverter
439431
touchTracker:_touchTracker
440432
dateProvider:_dateProvider
441433
delegate:self
442-
displayLinkWrapper:displayLinkWrapper
443-
environmentChecker:_environmentChecker];
434+
displayLinkWrapper:displayLinkWrapper];
444435

445436
[self.sessionReplay
446437
startWithRootView:[SentryDependencyContainer.sharedInstance.application getWindows]
@@ -472,15 +463,19 @@ - (nullable NSURL *)replayDirectory
472463
return [dir URLByAppendingPathComponent:SENTRY_REPLAY_FOLDER];
473464
}
474465

475-
- (void)saveCurrentSessionInfo:(SentryId *)sessionId
466+
- (void)saveCurrentSessionInfo:(SentryId *_Nullable)sessionId
476467
path:(NSString *)path
477468
options:(SentryReplayOptions *)options
478469
{
479470
SENTRY_LOG_DEBUG(@"[Session Replay] Saving current session info for session: %@ to path: %@",
480471
sessionId, path);
481-
NSDictionary *info =
482-
[[NSDictionary alloc] initWithObjectsAndKeys:sessionId.sentryIdString, @"replayId",
483-
path.lastPathComponent, @"path", @(options.onErrorSampleRate), @"errorSampleRate", nil];
472+
NSMutableDictionary *info = [NSMutableDictionary new];
473+
if (sessionId != nil) {
474+
[info setObject:SENTRY_UNWRAP_NULLABLE(SentryId, sessionId).sentryIdString
475+
forKey:@"replayId"];
476+
}
477+
[info setObject:path.lastPathComponent forKey:@"path"];
478+
[info setObject:@(options.onErrorSampleRate) forKey:@"errorSampleRate"];
484479

485480
NSData *data = [SentrySerializationSwift dataWithJSONObject:info];
486481

Sources/Sentry/include/HybridPublic/SentrySessionReplayIntegration.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ NS_ASSUME_NONNULL_BEGIN
4545

4646
- (void)hideMaskPreview;
4747

48+
/**
49+
* Verifies the device environment and options and returns wether it is safe to enable
50+
* SessionReplay or not
51+
*/
52+
+ (BOOL)shouldEnableForOptions:(SentryOptions *)options;
53+
4854
@end
4955
#endif // SENTRY_TARGET_REPLAY_SUPPORTED
5056
NS_ASSUME_NONNULL_END

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,12 @@ import UIKit
3030
private(set) var isSessionPaused = false
3131

3232
private let replayOptions: SentryReplayOptions
33-
private let experimentalOptions: SentryExperimentalOptions
3433
private let replayMaker: SentryReplayVideoMaker
3534
private let displayLink: SentryReplayDisplayLinkWrapper
3635
private let dateProvider: SentryCurrentDateProvider
3736
private let touchTracker: SentryTouchTracker?
3837
private let lock = NSLock()
3938
public var replayTags: [String: Any]?
40-
private let environmentChecker: SentrySessionReplayEnvironmentCheckerProvider
4139

4240
var isRunning: Bool {
4341
displayLink.isRunning()
@@ -48,19 +46,16 @@ import UIKit
4846

4947
public init(
5048
replayOptions: SentryReplayOptions,
51-
experimentalOptions: SentryExperimentalOptions,
5249
replayFolderPath: URL,
5350
screenshotProvider: SentryViewScreenshotProvider,
5451
replayMaker: SentryReplayVideoMaker,
5552
breadcrumbConverter: SentryReplayBreadcrumbConverter,
5653
touchTracker: SentryTouchTracker?,
5754
dateProvider: SentryCurrentDateProvider,
5855
delegate: SentrySessionReplayDelegate,
59-
displayLinkWrapper: SentryReplayDisplayLinkWrapper,
60-
environmentChecker: SentrySessionReplayEnvironmentCheckerProvider
56+
displayLinkWrapper: SentryReplayDisplayLinkWrapper
6157
) {
6258
self.replayOptions = replayOptions
63-
self.experimentalOptions = experimentalOptions
6459
self.dateProvider = dateProvider
6560
self.delegate = delegate
6661
self.screenshotProvider = screenshotProvider
@@ -69,27 +64,31 @@ import UIKit
6964
self.replayMaker = replayMaker
7065
self.breadcrumbConverter = breadcrumbConverter
7166
self.touchTracker = touchTracker
72-
self.environmentChecker = environmentChecker
7367
}
7468

7569
deinit { displayLink.invalidate() }
70+
71+
static public func shouldEnableSessionReplay(environmentChecker: SentrySessionReplayEnvironmentCheckerProvider, experimentalOptions: SentryExperimentalOptions) -> Bool {
72+
// Detect if we are running on iOS 26.0 with Liquid Glass and disable session replay.
73+
// This needs to be done until masking for session replay is properly supported, as it can lead
74+
// to PII leaks otherwise.
75+
if environmentChecker.isReliable() {
76+
return true
77+
}
78+
guard experimentalOptions.enableSessionReplayInUnreliableEnvironment else {
79+
SentrySDKLog.fatal("[Session Replay] Detected environment potentially causing PII leaks, disabling Session Replay. To override this mechanism, set `options.experimental.enableSessionReplayInUnreliableEnvironment` to `true`")
80+
return false
81+
}
82+
SentrySDKLog.warning("[Session Replay] Detected environment potentially causing PII leaks, but `options.experimental.enableSessionReplayInUnreliableEnvironment` is set to `true`, ignoring and enabling Session Replay.")
7683

84+
return true
85+
}
86+
7787
public func start(rootView: UIView, fullSession: Bool) {
7888
SentrySDKLog.debug("[Session Replay] Starting session replay with full session: \(fullSession)")
79-
guard !isRunning else {
89+
guard !isRunning else {
8090
SentrySDKLog.debug("[Session Replay] Session replay is already running, not starting again")
81-
return
82-
}
83-
84-
// Detect if we are running on iOS 26.0 with Liquid Glass and disable session replay.
85-
// This needs to be done until masking for session replay is properly supported, as it can lead
86-
// to PII leaks otherwise.
87-
if !environmentChecker.isReliable() {
88-
guard experimentalOptions.enableSessionReplayInUnreliableEnvironment else {
89-
SentrySDKLog.fatal("[Session Replay] Detected environment potentially causing PII leaks, disabling Session Replay. To override this mechanism, set `options.experimental.enableSessionReplayInUnreliableEnvironment` to `true`")
90-
return
91-
}
92-
SentrySDKLog.warning("[Session Replay] Detected environment potentially causing PII leaks, but `options.experimental.enableSessionReplayInUnreliableEnvironment` is set to `true`, ignoring and enabling Session Replay.")
91+
return
9392
}
9493

9594
displayLink.link(withTarget: self, selector: #selector(newFrame(_:)))

0 commit comments

Comments
 (0)