Skip to content

Commit 9ac9c48

Browse files
committed
feat: add FullDiskAccessVerifier and update CFBundleVersion to 6416;
ref: #206
1 parent 212ec84 commit 9ac9c48

File tree

7 files changed

+78
-22
lines changed

7 files changed

+78
-22
lines changed

Pareto Security.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
4F080D1D26C50D2800686786 /* ParetoSecurityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC81A4B26C41DC8006EABA8 /* ParetoSecurityTests.swift */; };
6262
4F0BD11F284891670098316A /* ClipButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0BD11E284891670098316A /* ClipButton.swift */; };
6363
4F0BD120284891670098316A /* ClipButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0BD11E284891670098316A /* ClipButton.swift */; };
64+
4F0CC3FA2E6F014200A0BE70 /* FullDiskAccessVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CC3F92E6F014200A0BE70 /* FullDiskAccessVerifier.swift */; };
65+
4F0CC3FB2E6F014200A0BE70 /* FullDiskAccessVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0CC3F92E6F014200A0BE70 /* FullDiskAccessVerifier.swift */; };
6466
4F103008269EE136008C1E86 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F103007269EE135008C1E86 /* SettingsView.swift */; };
6567
4F103012269F1C65008C1E86 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 4F103011269F1C65008C1E86 /* LaunchAtLogin */; };
6668
4F1192FD2D6DC4F4002CA181 /* ViewInspector in Frameworks */ = {isa = PBXBuildFile; productRef = 4F1192FC2D6DC4F4002CA181 /* ViewInspector */; };
@@ -364,6 +366,7 @@
364366
4F020DDE2853503400D8D4E9 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = "<group>"; };
365367
4F0371BB26CD00B700B35E43 /* Boot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Boot.swift; sourceTree = "<group>"; };
366368
4F0BD11E284891670098316A /* ClipButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipButton.swift; sourceTree = "<group>"; };
369+
4F0CC3F92E6F014200A0BE70 /* FullDiskAccessVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullDiskAccessVerifier.swift; sourceTree = "<group>"; };
367370
4F103007269EE135008C1E86 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
368371
4F14487326D8EA5E00A5BA34 /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
369372
4F14487B26D8EDB000A5BA34 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
@@ -719,6 +722,7 @@
719722
4FB7C67F26AFF22C00FB1C41 /* Extensions */ = {
720723
isa = PBXGroup;
721724
children = (
725+
4F0CC3F92E6F014200A0BE70 /* FullDiskAccessVerifier.swift */,
722726
4F7ACFC22DE080AB003400E8 /* HelperTool.swift */,
723727
4F7ACFAF2DE074FF003400E8 /* HelperProtocol.swift */,
724728
4FDDE1962D53BA3200D9E853 /* String.swift */,
@@ -1178,6 +1182,7 @@
11781182
4F2E09AE2BA330A80031422E /* Apps.swift in Sources */,
11791183
4F38D8C9273AC5AE00671756 /* IntroView.swift in Sources */,
11801184
4F37E71D2718122E00A2B254 /* Defaults.swift in Sources */,
1185+
4F0CC3FA2E6F014200A0BE70 /* FullDiskAccessVerifier.swift in Sources */,
11811186
4FDDE1982D53BA3200D9E853 /* String.swift in Sources */,
11821187
4F37E71E2718122E00A2B254 /* Autologin.swift in Sources */,
11831188
4F38D8CF273AC62500671756 /* EndView.swift in Sources */,
@@ -1325,6 +1330,7 @@
13251330
4F2E09AD2BA330A80031422E /* Apps.swift in Sources */,
13261331
4F38D8C8273AC5AE00671756 /* IntroView.swift in Sources */,
13271332
4F47221026B08A0E0071CE2A /* Defaults.swift in Sources */,
1333+
4F0CC3FB2E6F014200A0BE70 /* FullDiskAccessVerifier.swift in Sources */,
13281334
4FDDE1992D53BA3200D9E853 /* String.swift in Sources */,
13291335
4F868DD126A5C51C005B876E /* Autologin.swift in Sources */,
13301336
4F38D8CE273AC62500671756 /* EndView.swift in Sources */,

Pareto/Checks/Application Updates/ParetoUpdated.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,26 @@ class ParetoUpdated: ParetoCheck {
2727
override var TitleOFF: String {
2828
"Pareto Security is outdated"
2929
}
30-
30+
3131
override var infoURL: URL {
3232
var components = URLComponents()
3333
components.scheme = "https"
3434
components.host = "paretosecurity.com"
3535
components.path = "/check/\(UUID)"
36-
36+
3737
// Add version parameters
3838
var queryItems = [URLQueryItem]()
39-
39+
4040
// Add current and latest version information
4141
if !currentVersion.isEmpty {
4242
queryItems.append(URLQueryItem(name: "current_version", value: currentVersion))
4343
}
4444
if !latestVersion.isEmpty {
4545
queryItems.append(URLQueryItem(name: "latest_version", value: latestVersion))
4646
}
47-
47+
4848
components.queryItems = queryItems
49-
49+
5050
return components.url!
5151
}
5252

@@ -108,11 +108,11 @@ class ParetoUpdated: ParetoCheck {
108108
}
109109

110110
let latestVersionString = latestRelease.tag_name.replacingOccurrences(of: "v", with: "")
111-
111+
112112
// Store versions for URL construction
113-
self.currentVersion = appVersion
114-
self.latestVersion = latestVersionString
115-
113+
currentVersion = appVersion
114+
latestVersion = latestVersionString
115+
116116
let isUpToDate = appVersion == latestVersionString
117117

118118
os_log("Latest release is older than 10 days. App version: %{public}s, Latest version: %{public}s, Up to date: %{public}@",
@@ -125,10 +125,10 @@ class ParetoUpdated: ParetoCheck {
125125
appVersion = String(appVersion.split(separator: "-")[0])
126126
}
127127
let latestVersionString = latestRelease.tag_name.replacingOccurrences(of: "v", with: "")
128-
128+
129129
// Store versions for URL construction
130-
self.currentVersion = appVersion
131-
self.latestVersion = latestVersionString
130+
currentVersion = appVersion
131+
latestVersion = latestVersionString
132132

133133
os_log("Latest release is within 10 days grace period. Published: %{public}s, Days ago: %{public}f, App version: %{public}s, Latest version: %{public}s",
134134
latestRelease.published_at, abs(publishedDate.timeIntervalSinceNow) / (24 * 60 * 60), appVersion, latestVersionString)
@@ -144,7 +144,7 @@ class ParetoUpdated: ParetoCheck {
144144

145145
override func checkPasses() -> Bool {
146146
// Disable this check when running in SetApp or when beta channel is enabled
147-
147+
148148
#if SETAPP_ENABLED
149149
// Always pass for SetApp builds as updates are handled by SetApp
150150
return true
@@ -153,7 +153,7 @@ class ParetoUpdated: ParetoCheck {
153153
if Defaults[.showBeta] {
154154
return true
155155
}
156-
156+
157157
// Create a semaphore to wait for the async operation to complete
158158
let semaphore = DispatchSemaphore(value: 0)
159159

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// FullDiskAccessVerifier.swift
3+
// Pareto Security
4+
//
5+
// Created by Claude on 2025-09-08.
6+
//
7+
8+
import Foundation
9+
import os.log
10+
11+
enum FullDiskAccessVerifier {
12+
static func hasFullDiskAccess() -> Bool {
13+
let tmPlistPath = "/Library/Preferences/com.apple.TimeMachine.plist"
14+
15+
// Check if file exists
16+
guard FileManager.default.fileExists(atPath: tmPlistPath) else {
17+
os_log("FDA check: Time Machine plist does not exist")
18+
return false
19+
}
20+
21+
// Try to read as NSDictionary
22+
if let dict = NSDictionary(contentsOfFile: tmPlistPath) {
23+
if dict.count > 0 {
24+
os_log("FDA check: Successfully read Time Machine plist with %d entries", dict.count)
25+
return true
26+
}
27+
}
28+
29+
// Try using FileHandle
30+
if let handle = FileHandle(forReadingAtPath: tmPlistPath) {
31+
defer { handle.closeFile() }
32+
let data = handle.readData(ofLength: 10)
33+
if !data.isEmpty {
34+
os_log("FDA check: Read Time Machine plist via FileHandle")
35+
return true
36+
}
37+
}
38+
39+
// Try to read raw data
40+
if let data = try? Data(contentsOf: URL(fileURLWithPath: tmPlistPath)) {
41+
if !data.isEmpty {
42+
os_log("FDA check: Successfully read Time Machine plist data (%d bytes)", data.count)
43+
return true
44+
}
45+
}
46+
47+
os_log("FDA check: Cannot read Time Machine plist - No Full Disk Access")
48+
return false
49+
}
50+
}

Pareto/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</dict>
2727
</array>
2828
<key>CFBundleVersion</key>
29-
<string>6408</string>
29+
<string>6416</string>
3030
<key>LSApplicationCategoryType</key>
3131
<string>public.app-category.utilities</string>
3232
<key>LSMinimumSystemVersion</key>

Pareto/Views/Settings/AboutSettingsView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,19 @@ struct AboutSettingsView: View {
141141
}
142142
return
143143
}
144-
144+
145145
// Check if we can get the helper status from launchctl
146146
let launchctlOutput = await Task {
147147
runCMD(app: "/bin/launchctl", args: ["print", "system/co.niteo.ParetoSecurityHelper"])
148148
}.value
149-
149+
150150
if launchctlOutput.contains("state = not running") {
151151
await MainActor.run {
152152
self.helperVersion = "Needs authorization in System Settings"
153153
}
154154
return
155155
}
156-
156+
157157
// Set a timeout for the helper version fetch
158158
let timeoutTask = Task {
159159
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 second timeout
@@ -163,7 +163,7 @@ struct AboutSettingsView: View {
163163
}
164164
}
165165
}
166-
166+
167167
await helperManager.getHelperVersion { version in
168168
timeoutTask.cancel()
169169
Task { @MainActor in

Pareto/Views/Settings/PermissionsSettingsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct PermissionsSettingsView: View {
2121
@Default(.myChecksURL) var myChecksURL
2222
@Default(.hideWhenNoFailures) var hideWhenNoFailures
2323
@StateObject private var helperToolManager = HelperToolManager()
24-
@ObservedObject fileprivate var checker = PermissionsChecker()
24+
@StateObject private var checker = PermissionsChecker()
2525

2626
func authorizeOSAClick() {
2727
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation")!)

Pareto/Views/Welcome/PermissionsView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class PermissionsChecker: ObservableObject {
3333
func start() {
3434
timer?.invalidate() // cancel timer if any
3535
timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
36-
self.fdaAuthorized = TimeMachineHasBackupCheck.sharedInstance.isRunnable
36+
self.fdaAuthorized = FullDiskAccessVerifier.hasFullDiskAccess()
3737
self.osaAuthorized = self.osaIsAuthorized()
3838
self.firewallAuthorized = HelperToolUtilities.isHelperInstalled()
3939
self.ran = true
@@ -47,7 +47,7 @@ class PermissionsChecker: ObservableObject {
4747

4848
struct PermissionsView: View {
4949
@Binding var step: Steps
50-
@ObservedObject fileprivate var checker = PermissionsChecker()
50+
@StateObject private var checker = PermissionsChecker()
5151

5252
private var canContinue: Bool {
5353
// OSA is always required

0 commit comments

Comments
 (0)