Skip to content

Commit

Permalink
fix: detect and mask out photo library and user photos (#261)
Browse files Browse the repository at this point in the history
* fix: detect and mask out photo library and user photos

* chore: update CHANGELOG

* Update PostHog/Replay/PostHogReplayIntegration.swift

Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>

* feat: add maskPhotoLibraryImages and maskSandboxedViews config

* Update PostHog/Replay/PostHogSessionReplayConfig.swift

Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>

* fix: rename maskAllSandboxedViews config option

---------

Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>
  • Loading branch information
ioannisj and marandaneto authored Nov 25, 2024
1 parent f8d5524 commit d940967
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 8 deletions.
5 changes: 4 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ disabled_rules:
- trailing_comma
- opening_brace

line_length: 160
line_length:
warning: 160
ignores_comments: true

file_length:
warning: 1000
error: 1200
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- fix: detect and mask out system photo library and user photos ([#261](https://github.com/PostHog/posthog-ios/pull/261))

## 3.15.6 - 2024-11-20

- fix: read accessibilityLabel from parent's view to avoid performance hit on RN ([#259](https://github.com/PostHog/posthog-ios/pull/259))
Expand Down
51 changes: 44 additions & 7 deletions PostHog/Replay/PostHogReplayIntegration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//
#if os(iOS)
import Foundation
import PhotosUI
import SwiftUI
import UIKit
import WebKit
Expand Down Expand Up @@ -82,6 +83,8 @@

private let reactNativeTextView: AnyClass? = NSClassFromString("RCTTextView")
private let reactNativeImageView: AnyClass? = NSClassFromString("RCTImageView")
// These are usually views that don't belong to the current process and are most likely sensitive
private let systemSandboxedView: AnyClass? = NSClassFromString("_UIRemoteView")

static let dispatchQueue = DispatchQueue(label: "com.posthog.PostHogReplayIntegration",
target: .global(qos: .utility))
Expand Down Expand Up @@ -283,6 +286,15 @@
}
}

// detect any views that don't belong to the current process (likely system views)
if config.sessionReplayConfig.maskAllSandboxedViews,
let systemSandboxedView,
view.isKind(of: systemSandboxedView)
{
maskableWidgets.append(view.toAbsoluteRect(window))
return
}

// if its a generic type and has subviews, subviews have to be checked first
let hasSubViews = !view.subviews.isEmpty

Expand Down Expand Up @@ -380,6 +392,24 @@
image.imageAsset?.value(forKey: "_containingBundle") != nil
}

// Photo library images have a UUID identifier as _assetName (e.g 64EF5A48-2E96-4AB2-A79B-AAB7E9116E3D)
// SF symbol and bundle images have the actual symbol name as _assetName (e.g chevron.backward)
private func isPhotoLibraryImage(_ image: UIImage) -> Bool {
guard config.sessionReplayConfig.maskPhotoLibraryImages else {
return false
}

guard let assetName = image.imageAsset?.value(forKey: "_assetName") as? String else {
return false
}

if assetName.isEmpty { return false }
if image.isSymbolImage { return false }
if isAssetsImage(image) { return false }

return true
}

private func isAnyInputSensitive(_ view: UIView) -> Bool {
isTextInputSensitive(view) || config.sessionReplayConfig.maskAllImages
}
Expand Down Expand Up @@ -429,14 +459,21 @@
}

private func isImageViewSensitive(_ view: UIImageView) -> Bool {
var isAsset = false
if let image = view.image {
isAsset = isAssetsImage(image)
} else {
// if there's no image, there's nothing to mask
return false
// if there's no image, there's nothing to mask
guard let image = view.image else { return false }

// sensitive, regardless
if view.isNoCapture() {
return true
}
return (config.sessionReplayConfig.maskAllImages && !isAsset) || view.isNoCapture()

if config.sessionReplayConfig.maskAllImages {
// asset images are probably not sensitive
return !isAssetsImage(image)
}

// try to detect user photo images
return isPhotoLibraryImage(image)
}

private func toWireframe(_ view: UIView) -> RRWireframe? {
Expand Down
11 changes: 11 additions & 0 deletions PostHog/Replay/PostHogSessionReplayConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
/// Default: true
@objc public var maskAllImages: Bool = true

/// Enable masking of all sandboxed system views
/// These may include UIImagePickerController, PHPickerViewController and CNContactPickerViewController
/// Experimental support
/// Default: true
@objc public var maskAllSandboxedViews: Bool = true

/// Enable masking of images that likely originated from user's photo library
/// Experimental support (UIKit only)
/// Default: true
@objc public var maskPhotoLibraryImages: Bool = true

/// Enable capturing network telemetry
/// Experimental support
/// Default: true
Expand Down

0 comments on commit d940967

Please sign in to comment.