diff --git a/Cryptomator.xcodeproj/project.pbxproj b/Cryptomator.xcodeproj/project.pbxproj
index 536a03dc8..9f8817d8d 100644
--- a/Cryptomator.xcodeproj/project.pbxproj
+++ b/Cryptomator.xcodeproj/project.pbxproj
@@ -18,6 +18,9 @@
 		4A09BFC62684D599000E40AB /* VaultDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09BFC52684D599000E40AB /* VaultDetailItem.swift */; };
 		4A09E54C27071F3C0056D32A /* ErrorMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09E54B27071F3C0056D32A /* ErrorMapperTests.swift */; };
 		4A09E54E27071F4F0056D32A /* ErrorMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A09E54D27071F4F0056D32A /* ErrorMapper.swift */; };
+		4A0AA12B2AB8DB1800CF24FD /* PermissionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0AA12A2AB8DB1800CF24FD /* PermissionProvider.swift */; };
+		4A0AA12D2ABA277800CF24FD /* PermissionProviderImplTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0AA12C2ABA277800CF24FD /* PermissionProviderImplTests.swift */; };
+		4A0AA12F2ABA2A1600CF24FD /* PermissionProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0AA12E2ABA2A1600CF24FD /* PermissionProviderMock.swift */; };
 		4A0C07E225AC80C100B83211 /* UIView+Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07E125AC80C100B83211 /* UIView+Preview.swift */; };
 		4A0C07EB25AC832900B83211 /* VaultListPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0C07EA25AC832900B83211 /* VaultListPosition.swift */; };
 		4A0EAAD2296F604200E27B56 /* SessionTaskRegistratorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */; };
@@ -543,6 +546,9 @@
 		4A09BFC52684D599000E40AB /* VaultDetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultDetailItem.swift; sourceTree = "<group>"; };
 		4A09E54B27071F3C0056D32A /* ErrorMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMapperTests.swift; sourceTree = "<group>"; };
 		4A09E54D27071F4F0056D32A /* ErrorMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMapper.swift; sourceTree = "<group>"; };
+		4A0AA12A2AB8DB1800CF24FD /* PermissionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionProvider.swift; sourceTree = "<group>"; };
+		4A0AA12C2ABA277800CF24FD /* PermissionProviderImplTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionProviderImplTests.swift; sourceTree = "<group>"; };
+		4A0AA12E2ABA2A1600CF24FD /* PermissionProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionProviderMock.swift; sourceTree = "<group>"; };
 		4A0C07E125AC80C100B83211 /* UIView+Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Preview.swift"; sourceTree = "<group>"; };
 		4A0C07EA25AC832900B83211 /* VaultListPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VaultListPosition.swift; sourceTree = "<group>"; };
 		4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTaskRegistratorMock.swift; sourceTree = "<group>"; };
@@ -1192,6 +1198,7 @@
 				4A9C8DFC27A007C2000063E4 /* FileProviderNotificatorTests.swift */,
 				4AFBFA19282946BF00E30818 /* InMemoryProgressManagerTests.swift */,
 				4AB1D4EF27D20420009060AB /* LocalURLProviderTests.swift */,
+				4A0AA12C2ABA277800CF24FD /* PermissionProviderImplTests.swift */,
 				4AC1157727F5BEFD0023F51B /* Promise+AllIgnoringResultsTests.swift */,
 				4ADC66C427A7F6D6002E6CC7 /* UnlockMonitorTests.swift */,
 				4A4F47F224B875070033328B /* URL+NameCollisionExtensionTests.swift */,
@@ -1717,6 +1724,7 @@
 				4AEECD3E279EC48200C6E2B5 /* NSFileProviderChangeObserverMock.swift */,
 				4AA782E3282A9007001A71E3 /* NSFileProviderDomainProviderMock.swift */,
 				4AEECD3A279EB24300C6E2B5 /* NSFileProviderEnumerationObserverMock.swift */,
+				4A0AA12E2ABA2A1600CF24FD /* PermissionProviderMock.swift */,
 				4AFBFA172829414A00E30818 /* ProgressManagerMock.swift */,
 				4A0EAAD1296F604200E27B56 /* SessionTaskRegistratorMock.swift */,
 				4ADC66C627A95E67002E6CC7 /* UnlockMonitorTaskExecutorMock.swift */,
@@ -1901,6 +1909,7 @@
 				4AB1D4EB27D0E027009060AB /* LocalURLProviderType.swift */,
 				4AA782DD282A8250001A71E3 /* NSFileProviderDomainProvider.swift */,
 				4AEE6EE02822A33400E1B35E /* NSFileProviderItemIdentifier+Database.swift */,
+				4A0AA12A2AB8DB1800CF24FD /* PermissionProvider.swift */,
 				4AEE6EE92825716400E1B35E /* ProgressManager.swift */,
 				4AC1157527F5BD890023F51B /* Promise+AllIgnoringResult.swift */,
 				4ADD233F26737CD400374E4E /* RootFileProviderItem.swift */,
@@ -2537,6 +2546,7 @@
 				4AFBFA1628293FE200E30818 /* UploadRetryingServiceSourceTests.swift in Sources */,
 				4AB1C33C265E9DBC00DC7A49 /* CloudTaskExecutorTestCase.swift in Sources */,
 				4AE5196727F495BF00BA6E4A /* WorkflowDependencyTasksCollectionMock.swift in Sources */,
+				4A0AA12F2ABA2A1600CF24FD /* PermissionProviderMock.swift in Sources */,
 				4AC1157827F5BEFD0023F51B /* Promise+AllIgnoringResultsTests.swift in Sources */,
 				4AE5196527F48D6600BA6E4A /* WorkflowDependencyFactoryTests.swift in Sources */,
 				4A49FABE271ECDE80069A0CC /* ItemEnumerationTaskManagerTests.swift in Sources */,
@@ -2570,6 +2580,7 @@
 				4ADC66C527A7F6D6002E6CC7 /* UnlockMonitorTests.swift in Sources */,
 				4ABC08D7250D1EB600E3CEDC /* DeletionTaskManagerTests.swift in Sources */,
 				4A511D45265EB13B000A0E01 /* ItemEnumerationTaskTests.swift in Sources */,
+				4A0AA12D2ABA277800CF24FD /* PermissionProviderImplTests.swift in Sources */,
 				4A2F373724B47DB800460FD3 /* UploadTaskManagerTests.swift in Sources */,
 				4A248221266B8D37002D9F59 /* FileProviderAdapterImportDocumentTests.swift in Sources */,
 				4A511D5326615439000A0E01 /* ReparentTaskExecutorTests.swift in Sources */,
@@ -2935,6 +2946,7 @@
 				4A511D5D26668E47000A0E01 /* ReparentTaskRecord.swift in Sources */,
 				747F2F272587BC250072FB30 /* ReparentTask.swift in Sources */,
 				747F2F282587BC250072FB30 /* ReparentTaskDBManager.swift in Sources */,
+				4A0AA12B2AB8DB1800CF24FD /* PermissionProvider.swift in Sources */,
 				4AB1D4EC27D0E027009060AB /* LocalURLProviderType.swift in Sources */,
 				4A511D4E2660FF9E000A0E01 /* WorkflowScheduler.swift in Sources */,
 				4AD9481A2909A66900072110 /* MaintenanceModeHelperServiceSource.swift in Sources */,
diff --git a/Cryptomator/AddVault/Hub/HubAddVaultCoordinator.swift b/Cryptomator/AddVault/Hub/HubAddVaultCoordinator.swift
index e96a17901..9b8591d88 100644
--- a/Cryptomator/AddVault/Hub/HubAddVaultCoordinator.swift
+++ b/Cryptomator/AddVault/Hub/HubAddVaultCoordinator.swift
@@ -54,7 +54,9 @@ class AddHubVaultCoordinator: Coordinator {
 }
 
 extension AddHubVaultCoordinator: HubAuthenticationFlowDelegate {
-	func receivedExistingKey(jwe: JWE, privateKey: P384.KeyAgreement.PrivateKey) async {
+	func didSuccessfullyRemoteUnlock(_ response: HubUnlockResponse) async {
+		let jwe = response.jwe
+		let privateKey = response.privateKey
 		let hubVault = ExistingHubVault(vaultUID: vaultUID,
 		                                delegateAccountUID: accountUID,
 		                                jweData: jwe.compactSerializedData,
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift
index 0da86df3c..e8fcd36ad 100644
--- a/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/CryptomatorDatabase.swift
@@ -199,13 +199,9 @@ public class CryptomatorDatabase {
 	}
 
 	class func initialHubSupportMigration(_ db: Database) throws {
-		try db.create(table: "hubAccountInfo", body: { table in
-			table.column("userID", .text).primaryKey()
-		})
 		try db.create(table: "hubVaultAccount", body: { table in
-			table.column("id", .integer).primaryKey()
-			table.column("vaultUID", .text).notNull().unique().references("vaultAccounts", onDelete: .cascade)
-			table.column("hubUserID", .text).notNull().references("hubAccountInfo", onDelete: .cascade)
+			table.column("vaultUID", .text).primaryKey().references("vaultAccounts", onDelete: .cascade)
+			table.column("subscriptionState", .text).notNull()
 		})
 	}
 
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift
index 736a1b684..d7bea476a 100644
--- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift
@@ -12,7 +12,7 @@ import CryptomatorCloudAccessCore
 import Foundation
 
 public enum HubAuthenticationFlow {
-	case receivedExistingKey(Data)
+	case success(Data, [AnyHashable: Any])
 	case accessNotGranted
 	case needsDeviceRegistration
 	case licenseExceeded
@@ -53,9 +53,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving
 		var urlRequest = URLRequest(url: url)
 		urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"]
 		let (data, response) = try await URLSession.shared.data(with: urlRequest)
-		switch (response as? HTTPURLResponse)?.statusCode {
+		let httpResponse = response as? HTTPURLResponse
+		switch httpResponse?.statusCode {
 		case 200:
-			return .receivedExistingKey(data)
+			return .success(data, httpResponse?.allHeaderFields ?? [:])
 		case 402:
 			return .licenseExceeded
 		case 403:
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationFlowDelegate.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationFlowDelegate.swift
index 1e37d9d2b..8269b4726 100644
--- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationFlowDelegate.swift
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationFlowDelegate.swift
@@ -2,5 +2,11 @@ import CryptoKit
 import JOSESwift
 
 public protocol HubAuthenticationFlowDelegate: AnyObject {
-	func receivedExistingKey(jwe: JWE, privateKey: P384.KeyAgreement.PrivateKey) async
+	func didSuccessfullyRemoteUnlock(_ response: HubUnlockResponse) async
+}
+
+public struct HubUnlockResponse {
+	public let jwe: JWE
+	public let privateKey: P384.KeyAgreement.PrivateKey
+	public let subscriptionState: HubSubscriptionState
 }
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift
index 7b2f59031..449b10bd3 100644
--- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift
@@ -1,4 +1,5 @@
 import AppAuthCore
+import CocoaLumberjackSwift
 import CryptoKit
 import CryptomatorCloudAccessCore
 import Foundation
@@ -8,6 +9,8 @@ import UIKit
 public enum HubAuthenticationViewModelError: Error {
 	case missingHubConfig
 	case missingAuthState
+	case missingSubscriptionHeader
+	case unexpectedSubscriptionHeader
 }
 
 public class HubAuthenticationViewModel: ObservableObject {
@@ -25,6 +28,10 @@ public class HubAuthenticationViewModel: ObservableObject {
 		case needsAuthorization
 	}
 
+	private enum Constants {
+		static var subscriptionState: String { "hub-subscription-state" }
+	}
+
 	@Published var authenticationFlowState: State = .userLogin
 	@Published public var deviceName: String = UIDevice.current.name
 
@@ -101,8 +108,8 @@ public class HubAuthenticationViewModel: ObservableObject {
 			return
 		}
 		switch authFlow {
-		case let .receivedExistingKey(data):
-			await receivedExistingKey(data: data)
+		case let .success(data, header):
+			await receivedExistingKey(data: data, header: header)
 		case .accessNotGranted:
 			await setState(to: .accessNotGranted)
 		case .needsDeviceRegistration:
@@ -112,17 +119,22 @@ public class HubAuthenticationViewModel: ObservableObject {
 		}
 	}
 
-	private func receivedExistingKey(data: Data) async {
+	private func receivedExistingKey(data: Data, header: [AnyHashable: Any]) async {
 		let privateKey: P384.KeyAgreement.PrivateKey
 		let jwe: JWE
+		let subscriptionState: HubSubscriptionState
 		do {
 			privateKey = try CryptomatorHubKeyProvider.shared.getPrivateKey()
 			jwe = try JWE(compactSerialization: data)
+			subscriptionState = try getSubscriptionState(from: header)
 		} catch {
 			await setStateToErrorState(with: error)
 			return
 		}
-		await delegate?.receivedExistingKey(jwe: jwe, privateKey: privateKey)
+		let response = HubUnlockResponse(jwe: jwe,
+		                                 privateKey: privateKey,
+		                                 subscriptionState: subscriptionState)
+		await delegate?.didSuccessfullyRemoteUnlock(response)
 	}
 
 	@MainActor
@@ -133,4 +145,20 @@ public class HubAuthenticationViewModel: ObservableObject {
 	private func setStateToErrorState(with error: Error) async {
 		await setState(to: .error(description: error.localizedDescription))
 	}
+
+	private func getSubscriptionState(from header: [AnyHashable: Any]) throws -> HubSubscriptionState {
+		guard let subscriptionStateValue = header[Constants.subscriptionState] as? String else {
+			DDLogError("Can't retrieve hub subscription state from header -> missing value")
+			throw HubAuthenticationViewModelError.missingSubscriptionHeader
+		}
+		switch subscriptionStateValue {
+		case "ACTIVE":
+			return .active
+		case "INACTIVE":
+			return .inactive
+		default:
+			DDLogError("Can't retrieve hub subscription state from header -> unexpected value")
+			throw HubAuthenticationViewModelError.unexpectedSubscriptionHeader
+		}
+	}
 }
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubRepository.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubRepository.swift
new file mode 100644
index 000000000..f44ee2488
--- /dev/null
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubRepository.swift
@@ -0,0 +1,72 @@
+import Dependencies
+import Foundation
+import GRDB
+
+public protocol HubRepository {
+	func save(_ vault: HubVault) throws
+	func getHubVault(vaultID: String) throws -> HubVault?
+}
+
+public struct HubVault: Equatable {
+	public let vaultUID: String
+	public let subscriptionState: HubSubscriptionState
+}
+
+private struct HubVaultRow: Codable, Equatable, PersistableRecord, FetchableRecord {
+	public static let databaseTableName = "hubVaultAccount"
+
+	let vaultUID: String
+	let subscriptionState: HubSubscriptionState
+
+	init(from vault: HubVault) {
+		self.vaultUID = vault.vaultUID
+		self.subscriptionState = vault.subscriptionState
+	}
+
+	func toHubVault() -> HubVault {
+		HubVault(vaultUID: vaultUID, subscriptionState: subscriptionState)
+	}
+
+	enum Columns: String, ColumnExpression {
+		case vaultUID, subscriptionState
+	}
+
+	public func encode(to container: inout PersistenceContainer) {
+		container[Columns.vaultUID] = vaultUID
+		container[Columns.subscriptionState] = subscriptionState
+	}
+}
+
+extension HubSubscriptionState: DatabaseValueConvertible {}
+
+public extension DependencyValues {
+	var hubRepository: HubRepository {
+		get { self[HubRepositoryKey.self] }
+		set { self[HubRepositoryKey.self] = newValue }
+	}
+}
+
+private enum HubRepositoryKey: DependencyKey {
+	static var liveValue: HubRepository = HubDBRepository()
+	#if DEBUG
+	static var testValue: HubRepository = HubRepositoryMock()
+	#endif
+}
+
+public class HubDBRepository: HubRepository {
+	@Dependency(\.database) private var database
+
+	public func save(_ vault: HubVault) throws {
+		let row = HubVaultRow(from: vault)
+		try database.write { db in
+			try row.save(db)
+		}
+	}
+
+	public func getHubVault(vaultID: String) throws -> HubVault? {
+		let row = try database.read { db in
+			try HubVaultRow.fetchOne(db, key: vaultID)
+		}
+		return row?.toHubVault()
+	}
+}
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubSubscriptionState.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubSubscriptionState.swift
new file mode 100644
index 000000000..daf4d3185
--- /dev/null
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubSubscriptionState.swift
@@ -0,0 +1,4 @@
+public enum HubSubscriptionState: String, Codable {
+	case active
+	case inactive
+}
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubXPCLoginCoordinator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubXPCLoginCoordinator.swift
index 9ecb8dbd3..9249a61ec 100644
--- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubXPCLoginCoordinator.swift
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubXPCLoginCoordinator.swift
@@ -2,6 +2,7 @@ import AppAuthCore
 import CryptoKit
 import CryptomatorCloudAccessCore
 import CryptomatorCryptoLib
+import Dependencies
 import JOSESwift
 import SwiftUI
 import UIKit
@@ -15,6 +16,7 @@ public final class HubXPCLoginCoordinator: Coordinator {
 	let hubAuthenticator: HubAuthenticating
 	public let onUnlocked: () -> Void
 	public let onErrorAlertDismissed: () -> Void
+	@Dependency(\.hubRepository) private var hubRepository
 
 	public init(navigationController: UINavigationController,
 	            domain: NSFileProviderDomain,
@@ -42,25 +44,26 @@ public final class HubXPCLoginCoordinator: Coordinator {
 }
 
 extension HubXPCLoginCoordinator: HubAuthenticationFlowDelegate {
-	public func receivedExistingKey(jwe: JWE, privateKey: P384.KeyAgreement.PrivateKey) async {
+	public func didSuccessfullyRemoteUnlock(_ response: HubUnlockResponse) async {
 		let masterkey: Masterkey
 		do {
-			masterkey = try JWEHelper.decrypt(jwe: jwe, with: privateKey)
+			masterkey = try JWEHelper.decrypt(jwe: response.jwe, with: response.privateKey)
 		} catch {
 			handleError(error, for: navigationController, onOKTapped: onErrorAlertDismissed)
 			return
 		}
-		let xpc: XPC<VaultUnlocking>
 		do {
-			xpc = try await fileProviderConnector.getXPC(serviceName: .vaultUnlocking, domain: domain)
+			let xpc: XPC<VaultUnlocking> = try await fileProviderConnector.getXPC(serviceName: .vaultUnlocking, domain: domain)
 			defer {
 				fileProviderConnector.invalidateXPC(xpc)
 			}
 			try await xpc.proxy.unlockVault(rawKey: masterkey.rawKey).getValue()
-			fileProviderConnector.invalidateXPC(xpc)
+			let hubVault = HubVault(vaultUID: domain.identifier.rawValue, subscriptionState: response.subscriptionState)
+			try hubRepository.save(hubVault)
 			onUnlocked()
 		} catch {
 			handleError(error, for: navigationController, onOKTapped: onErrorAlertDismissed)
+			return
 		}
 	}
 }
diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/HubRepositoryMock.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/HubRepositoryMock.swift
new file mode 100644
index 000000000..92e0d7896
--- /dev/null
+++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/HubRepositoryMock.swift
@@ -0,0 +1,53 @@
+import Foundation
+
+#if DEBUG
+
+// MARK: - HubRepositoryMock -
+
+final class HubRepositoryMock: HubRepository {
+	// MARK: - save
+
+	var saveThrowableError: Error?
+	var saveCallsCount = 0
+	var saveCalled: Bool {
+		saveCallsCount > 0
+	}
+
+	var saveReceivedVault: HubVault?
+	var saveReceivedInvocations: [HubVault] = []
+	var saveClosure: ((HubVault) throws -> Void)?
+
+	func save(_ vault: HubVault) throws {
+		if let error = saveThrowableError {
+			throw error
+		}
+		saveCallsCount += 1
+		saveReceivedVault = vault
+		saveReceivedInvocations.append(vault)
+		try saveClosure?(vault)
+	}
+
+	// MARK: - getHubVault
+
+	var getHubVaultVaultIDThrowableError: Error?
+	var getHubVaultVaultIDCallsCount = 0
+	var getHubVaultVaultIDCalled: Bool {
+		getHubVaultVaultIDCallsCount > 0
+	}
+
+	var getHubVaultVaultIDReceivedVaultID: String?
+	var getHubVaultVaultIDReceivedInvocations: [String] = []
+	var getHubVaultVaultIDReturnValue: HubVault?
+	var getHubVaultVaultIDClosure: ((String) throws -> HubVault?)?
+
+	func getHubVault(vaultID: String) throws -> HubVault? {
+		if let error = getHubVaultVaultIDThrowableError {
+			throw error
+		}
+		getHubVaultVaultIDCallsCount += 1
+		getHubVaultVaultIDReceivedVaultID = vaultID
+		getHubVaultVaultIDReceivedInvocations.append(vaultID)
+		return try getHubVaultVaultIDClosure.map({ try $0(vaultID) }) ?? getHubVaultVaultIDReturnValue
+	}
+}
+#endif
diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubDBRepositoryTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubDBRepositoryTests.swift
new file mode 100644
index 000000000..211b2f87f
--- /dev/null
+++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubDBRepositoryTests.swift
@@ -0,0 +1,89 @@
+import GRDB
+import XCTest
+@testable import CryptomatorCommonCore
+
+final class HubDBRepositoryTests: XCTestCase {
+	private var inMemoryDB: DatabaseQueue!
+	private var repository: HubDBRepository!
+	private var vaultAccountManager: VaultAccountManager!
+	private var cloudAccountManager: CloudProviderAccountManager!
+
+	override func setUpWithError() throws {
+		repository = HubDBRepository()
+		vaultAccountManager = VaultAccountDBManager()
+		cloudAccountManager = CloudProviderAccountDBManager()
+	}
+
+	func testSaveAndRetrieve() throws {
+		// GIVEN
+		// a cloud account has been created
+		let cloudAccount = CloudProviderAccount(accountUID: "", cloudProviderType: .dropbox)
+		try cloudAccountManager.saveNewAccount(cloudAccount)
+
+		// and a vault account has been created
+		let vaultID = "123456789"
+		let vaultAccount = VaultAccount(vaultUID: vaultID, delegateAccountUID: "", vaultPath: .init(""), vaultName: "")
+		try vaultAccountManager.saveNewAccount(vaultAccount)
+
+		// WHEN
+		// saving a hub vault
+		let vault = HubVault(vaultUID: vaultID, subscriptionState: .active)
+		try repository.save(vault)
+
+		// THEN
+		// it can be retrieved
+		let retrievedVault = try repository.getHubVault(vaultID: vaultID)
+		XCTAssertEqual(vault, retrievedVault)
+	}
+
+	func testSaveToUpdate() throws {
+		// GIVEN
+		// a cloud account has been created
+		let cloudAccount = CloudProviderAccount(accountUID: "", cloudProviderType: .dropbox)
+		try cloudAccountManager.saveNewAccount(cloudAccount)
+
+		// and a vault account has been created
+		let vaultID = "123456789"
+		let vaultAccount = VaultAccount(vaultUID: vaultID, delegateAccountUID: "", vaultPath: .init(""), vaultName: "")
+		try vaultAccountManager.saveNewAccount(vaultAccount)
+
+		// WHEN
+		// saving a hub vault
+		let initialVault = HubVault(vaultUID: vaultID, subscriptionState: .active)
+		try repository.save(initialVault)
+
+		// and saving the hub vault with the same vault ID but a changed subscription state
+		let updatedVault = HubVault(vaultUID: vaultID, subscriptionState: .inactive)
+		try repository.save(updatedVault)
+
+		// THEN
+		// it the updated version can be retrieved
+		let retrievedVault = try repository.getHubVault(vaultID: vaultID)
+		XCTAssertEqual(updatedVault, retrievedVault)
+	}
+
+	func testDeleteVaultAccountAlsoDeletesHubVault() throws {
+		// GIVEN
+		// a cloud account has been created
+		let cloudAccount = CloudProviderAccount(accountUID: "", cloudProviderType: .dropbox)
+		try cloudAccountManager.saveNewAccount(cloudAccount)
+
+		// and a vault account has been created
+		let vaultID = "123456789"
+		let vaultAccount = VaultAccount(vaultUID: vaultID, delegateAccountUID: "", vaultPath: .init(""), vaultName: "")
+		try vaultAccountManager.saveNewAccount(vaultAccount)
+
+		// and a hub vault has been created for the vault id
+		let vault = HubVault(vaultUID: vaultID, subscriptionState: .active)
+		try repository.save(vault)
+
+		// WHEN
+		// the vault account gets deleted
+		try vaultAccountManager.removeAccount(with: vaultID)
+
+		// THEN
+		// the hub vault account has been deleted and can not be retrieved
+		let retrievedVault = try repository.getHubVault(vaultID: vaultID)
+		XCTAssertNil(retrievedVault)
+	}
+}
diff --git a/CryptomatorFileProvider/DB/WorkingSetObserver.swift b/CryptomatorFileProvider/DB/WorkingSetObserver.swift
index 8b35d1d62..b4f411807 100644
--- a/CryptomatorFileProvider/DB/WorkingSetObserver.swift
+++ b/CryptomatorFileProvider/DB/WorkingSetObserver.swift
@@ -7,6 +7,7 @@
 //
 
 import CocoaLumberjackSwift
+import Dependencies
 import FileProvider
 import Foundation
 import GRDB
@@ -23,8 +24,13 @@ class WorkingSetObserver: WorkingSetObserving {
 	private let notificator: FileProviderNotificatorType
 	private var currentWorkingSetItems = Set<FileProviderItem>()
 	private let domainIdentifier: NSFileProviderDomainIdentifier
+	@Dependency(\.permissionProvider) private var permissionProvider
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, database: DatabaseReader, notificator: FileProviderNotificatorType, uploadTaskManager: UploadTaskManager, cachedFileManager: CachedFileManager) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     database: DatabaseReader,
+	     notificator: FileProviderNotificatorType,
+	     uploadTaskManager: UploadTaskManager,
+	     cachedFileManager: CachedFileManager) {
 		self.domainIdentifier = domainIdentifier
 		self.database = database
 		self.notificator = notificator
diff --git a/CryptomatorFileProvider/FileProviderAdapter.swift b/CryptomatorFileProvider/FileProviderAdapter.swift
index ce2564f5d..2ff7e112c 100644
--- a/CryptomatorFileProvider/FileProviderAdapter.swift
+++ b/CryptomatorFileProvider/FileProviderAdapter.swift
@@ -74,6 +74,7 @@ public class FileProviderAdapter: FileProviderAdapterType {
 	private let domainIdentifier: NSFileProviderDomainIdentifier
 	private let fileCoordinator: NSFileCoordinator
 	private let taskRegistrator: SessionTaskRegistrator
+	@Dependency(\.permissionProvider) private var permissionProvider
 
 	init(domainIdentifier: NSFileProviderDomainIdentifier,
 	     uploadTaskManager: UploadTaskManager,
diff --git a/CryptomatorFileProvider/FileProviderAdapterManager.swift b/CryptomatorFileProvider/FileProviderAdapterManager.swift
index 660bf9626..d53e08185 100644
--- a/CryptomatorFileProvider/FileProviderAdapterManager.swift
+++ b/CryptomatorFileProvider/FileProviderAdapterManager.swift
@@ -9,6 +9,7 @@
 import CocoaLumberjackSwift
 import CryptomatorCloudAccessCore
 import CryptomatorCommonCore
+import Dependencies
 import FileProvider
 import Foundation
 import GRDB
@@ -32,12 +33,27 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding {
 	private let notificatorManager: FileProviderNotificatorManagerType
 	private let queue = DispatchQueue(label: "FileProviderAdapterManager", qos: .userInitiated)
 	private let providerIdentifier: String
+	@Dependency(\.permissionProvider) private var permissionProvider
 
 	convenience init() {
-		self.init(masterkeyCacheManager: MasterkeyCacheKeychainManager.shared, vaultKeepUnlockedHelper: VaultKeepUnlockedManager.shared, vaultKeepUnlockedSettings: VaultKeepUnlockedManager.shared, vaultManager: VaultDBManager.shared, adapterCache: FileProviderAdapterCache(), notificatorManager: FileProviderNotificatorManager.shared, unlockMonitor: UnlockMonitor(), providerIdentifier: NSFileProviderManager.default.providerIdentifier)
+		self.init(masterkeyCacheManager: MasterkeyCacheKeychainManager.shared,
+		          vaultKeepUnlockedHelper: VaultKeepUnlockedManager.shared,
+		          vaultKeepUnlockedSettings: VaultKeepUnlockedManager.shared,
+		          vaultManager: VaultDBManager.shared,
+		          adapterCache: FileProviderAdapterCache(),
+		          notificatorManager: FileProviderNotificatorManager.shared,
+		          unlockMonitor: UnlockMonitor(),
+		          providerIdentifier: NSFileProviderManager.default.providerIdentifier)
 	}
 
-	init(masterkeyCacheManager: MasterkeyCacheManager, vaultKeepUnlockedHelper: VaultKeepUnlockedHelper, vaultKeepUnlockedSettings: VaultKeepUnlockedSettings, vaultManager: VaultManager, adapterCache: FileProviderAdapterCacheType, notificatorManager: FileProviderNotificatorManagerType, unlockMonitor: UnlockMonitorType, providerIdentifier: String) {
+	init(masterkeyCacheManager: MasterkeyCacheManager,
+	     vaultKeepUnlockedHelper: VaultKeepUnlockedHelper,
+	     vaultKeepUnlockedSettings: VaultKeepUnlockedSettings,
+	     vaultManager: VaultManager,
+	     adapterCache: FileProviderAdapterCacheType,
+	     notificatorManager: FileProviderNotificatorManagerType,
+	     unlockMonitor: UnlockMonitorType,
+	     providerIdentifier: String) {
 		self.masterkeyCacheManager = masterkeyCacheManager
 		self.vaultKeepUnlockedHelper = vaultKeepUnlockedHelper
 		self.vaultKeepUnlockedSettings = vaultKeepUnlockedSettings
@@ -190,7 +206,12 @@ public class FileProviderAdapterManager: FileProviderAdapterProviding {
 		                                  notificator: notificator,
 		                                  localURLProvider: delegate,
 		                                  taskRegistrator: taskRegistrator)
-		let workingSetObserver = WorkingSetObserver(domainIdentifier: domainIdentifier, database: database, notificator: notificator, uploadTaskManager: uploadTaskManager, cachedFileManager: cachedFileManager)
+
+		let workingSetObserver = WorkingSetObserver(domainIdentifier: domainIdentifier,
+		                                            database: database,
+		                                            notificator: notificator,
+		                                            uploadTaskManager: uploadTaskManager,
+		                                            cachedFileManager: cachedFileManager)
 		workingSetObserver.startObservation()
 		return AdapterCacheItem(adapter: adapter, maintenanceManager: maintenanceManager, workingSetObserver: workingSetObserver)
 	}
diff --git a/CryptomatorFileProvider/FileProviderItem.swift b/CryptomatorFileProvider/FileProviderItem.swift
index 64c822b6f..2b0bc5955 100644
--- a/CryptomatorFileProvider/FileProviderItem.swift
+++ b/CryptomatorFileProvider/FileProviderItem.swift
@@ -24,6 +24,7 @@ public class FileProviderItem: NSObject, NSFileProviderItem {
 	let localURL: URL?
 	let domainIdentifier: NSFileProviderDomainIdentifier
 	@Dependency(\.fullVersionChecker) private var fullVersionChecker
+	@Dependency(\.permissionProvider) private var permissionProvider
 
 	init(metadata: ItemMetadata, domainIdentifier: NSFileProviderDomainIdentifier, newestVersionLocallyCached: Bool = false, localURL: URL? = nil, error: Error? = nil) {
 		self.metadata = metadata
@@ -50,19 +51,7 @@ public class FileProviderItem: NSObject, NSFileProviderItem {
 	}
 
 	public var capabilities: NSFileProviderItemCapabilities {
-		if metadata.statusCode == .uploadError {
-			return .allowsDeleting
-		}
-		if !fullVersionChecker.isFullVersion {
-			return FileProviderItem.readOnlyCapabilities
-		}
-		if metadata.type == .folder {
-			return [.allowsAddingSubItems, .allowsContentEnumerating, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting]
-		}
-		if metadata.statusCode == .isUploading {
-			return .allowsReading
-		}
-		return [.allowsWriting, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting]
+		return permissionProvider.getPermissions(for: metadata, at: domainIdentifier)
 	}
 
 	public var filename: String {
diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift
index c3feed4e7..6e1c23446 100644
--- a/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift
+++ b/CryptomatorFileProvider/Middleware/TaskExecutor/DownloadTaskExecutor.swift
@@ -8,6 +8,7 @@
 
 import CocoaLumberjackSwift
 import CryptomatorCloudAccessCore
+import Dependencies
 import Foundation
 import Promises
 
@@ -30,8 +31,13 @@ class DownloadTaskExecutor: WorkflowMiddleware {
 	private let downloadTaskManager: DownloadTaskManager
 	private let provider: CloudProvider
 	private let domainIdentifier: NSFileProviderDomainIdentifier
+	@Dependency(\.permissionProvider) private var permissionProvider
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, provider: CloudProvider, itemMetadataManager: ItemMetadataManager, cachedFileManager: CachedFileManager, downloadTaskManager: DownloadTaskManager) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     provider: CloudProvider,
+	     itemMetadataManager: ItemMetadataManager,
+	     cachedFileManager: CachedFileManager,
+	     downloadTaskManager: DownloadTaskManager) {
 		self.domainIdentifier = domainIdentifier
 		self.provider = provider
 		self.itemMetadataManager = itemMetadataManager
diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/FolderCreationTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/FolderCreationTaskExecutor.swift
index 23235e3b6..fd2623508 100644
--- a/CryptomatorFileProvider/Middleware/TaskExecutor/FolderCreationTaskExecutor.swift
+++ b/CryptomatorFileProvider/Middleware/TaskExecutor/FolderCreationTaskExecutor.swift
@@ -29,7 +29,9 @@ class FolderCreationTaskExecutor: WorkflowMiddleware {
 	private let provider: CloudProvider
 	private let domainIdentifier: NSFileProviderDomainIdentifier
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, provider: CloudProvider, itemMetadataManager: ItemMetadataManager) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     provider: CloudProvider,
+	     itemMetadataManager: ItemMetadataManager) {
 		self.domainIdentifier = domainIdentifier
 		self.provider = provider
 		self.itemMetadataManager = itemMetadataManager
@@ -53,11 +55,13 @@ class FolderCreationTaskExecutor: WorkflowMiddleware {
 		assert(itemMetadata.id != nil)
 		assert(itemMetadata.type == .folder)
 
-		return provider.createFolder(at: itemMetadata.cloudPath).then { _ -> FileProviderItem in
+		return provider.createFolder(at: itemMetadata.cloudPath).then { [domainIdentifier, itemMetadataManager] _ -> FileProviderItem in
 			itemMetadata.statusCode = .isUploaded
 			itemMetadata.isPlaceholderItem = false
-			try self.itemMetadataManager.updateMetadata(itemMetadata)
-			return FileProviderItem(metadata: itemMetadata, domainIdentifier: self.domainIdentifier, newestVersionLocallyCached: true)
+			try itemMetadataManager.updateMetadata(itemMetadata)
+			return FileProviderItem(metadata: itemMetadata,
+			                        domainIdentifier: domainIdentifier,
+			                        newestVersionLocallyCached: true)
 		}
 	}
 }
diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/ItemEnumerationTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/ItemEnumerationTaskExecutor.swift
index db91a48d7..5d4e96be5 100644
--- a/CryptomatorFileProvider/Middleware/TaskExecutor/ItemEnumerationTaskExecutor.swift
+++ b/CryptomatorFileProvider/Middleware/TaskExecutor/ItemEnumerationTaskExecutor.swift
@@ -37,7 +37,15 @@ class ItemEnumerationTaskExecutor: WorkflowMiddleware {
 	private let provider: CloudProvider
 	private let domainIdentifier: NSFileProviderDomainIdentifier
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, provider: CloudProvider, itemMetadataManager: ItemMetadataManager, cachedFileManager: CachedFileManager, uploadTaskManager: UploadTaskManager, reparentTaskManager: ReparentTaskManager, deletionTaskManager: DeletionTaskManager, itemEnumerationTaskManager: ItemEnumerationTaskManager, deleteItemHelper: DeleteItemHelper) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     provider: CloudProvider,
+	     itemMetadataManager: ItemMetadataManager,
+	     cachedFileManager: CachedFileManager,
+	     uploadTaskManager: UploadTaskManager,
+	     reparentTaskManager: ReparentTaskManager,
+	     deletionTaskManager: DeletionTaskManager,
+	     itemEnumerationTaskManager: ItemEnumerationTaskManager,
+	     deleteItemHelper: DeleteItemHelper) {
 		self.domainIdentifier = domainIdentifier
 		self.provider = provider
 		self.itemMetadataManager = itemMetadataManager
diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/ReparentTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/ReparentTaskExecutor.swift
index 6593c372a..7b85d9a3c 100644
--- a/CryptomatorFileProvider/Middleware/TaskExecutor/ReparentTaskExecutor.swift
+++ b/CryptomatorFileProvider/Middleware/TaskExecutor/ReparentTaskExecutor.swift
@@ -7,6 +7,7 @@
 //
 
 import CryptomatorCloudAccessCore
+import Dependencies
 import FileProvider
 import Foundation
 import Promises
@@ -30,8 +31,13 @@ class ReparentTaskExecutor: WorkflowMiddleware {
 	private let itemMetadataManager: ItemMetadataManager
 	private let cachedFileManager: CachedFileManager
 	private let domainIdentifier: NSFileProviderDomainIdentifier
+	@Dependency(\.permissionProvider) private var permissionProvider
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, provider: CloudProvider, reparentTaskManager: ReparentTaskManager, itemMetadataManager: ItemMetadataManager, cachedFileManager: CachedFileManager) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     provider: CloudProvider,
+	     reparentTaskManager: ReparentTaskManager,
+	     itemMetadataManager: ItemMetadataManager,
+	     cachedFileManager: CachedFileManager) {
 		self.domainIdentifier = domainIdentifier
 		self.provider = provider
 		self.reparentTaskManager = reparentTaskManager
diff --git a/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift b/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift
index a670cc153..2fc5abf38 100644
--- a/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift
+++ b/CryptomatorFileProvider/Middleware/TaskExecutor/UploadTaskExecutor.swift
@@ -8,6 +8,7 @@
 
 import CocoaLumberjackSwift
 import CryptomatorCloudAccessCore
+import Dependencies
 import FileProvider
 import Foundation
 import Promises
@@ -32,8 +33,14 @@ class UploadTaskExecutor: WorkflowMiddleware {
 	let uploadTaskManager: UploadTaskManager
 	let domainIdentifier: NSFileProviderDomainIdentifier
 	let progressManager: ProgressManager
+	@Dependency(\.permissionProvider) private var permissionProvider
 
-	init(domainIdentifier: NSFileProviderDomainIdentifier, provider: CloudProvider, cachedFileManager: CachedFileManager, itemMetadataManager: ItemMetadataManager, uploadTaskManager: UploadTaskManager, progressManager: ProgressManager = InMemoryProgressManager.shared) {
+	init(domainIdentifier: NSFileProviderDomainIdentifier,
+	     provider: CloudProvider,
+	     cachedFileManager: CachedFileManager,
+	     itemMetadataManager: ItemMetadataManager,
+	     uploadTaskManager: UploadTaskManager,
+	     progressManager: ProgressManager = InMemoryProgressManager.shared) {
 		self.domainIdentifier = domainIdentifier
 		self.provider = provider
 		self.cachedFileManager = cachedFileManager
diff --git a/CryptomatorFileProvider/PermissionProvider.swift b/CryptomatorFileProvider/PermissionProvider.swift
new file mode 100644
index 000000000..bcdbee887
--- /dev/null
+++ b/CryptomatorFileProvider/PermissionProvider.swift
@@ -0,0 +1,127 @@
+//
+//  PermissionProvider.swift
+//  CryptomatorFileProvider
+//
+//  Created by Philipp Schmid on 18.09.23.
+//  Copyright © 2023 Skymatic GmbH. All rights reserved.
+//
+
+import CocoaLumberjackSwift
+import CryptomatorCommonCore
+import Dependencies
+import FileProvider
+import Foundation
+
+public protocol PermissionProvider {
+	/**
+	 Returns the permission for a given `item` at a given `domain`.
+
+	 The following restrictions can apply to any item:
+	 - in case of an upload error it's only allowed to delete the item.
+	 - in case of a free version only reading is allowed, except if the vault belongs to Cryptomator Hub and it has an active subscription state.
+
+	 The following capabilities hold for files:
+	 - reading
+	 - adding sub items
+	 - content enumerating
+	 - deleting
+	 - renaming
+	 - reparenting
+
+	 - Note: In case of an running upload, i.e. a creation of the folder in the cloud, the capabilities do not get restricted except if something listed above restricts all items of the vault.
+
+	 The following capabilities hold for files:
+	 - reading
+	 - writing
+	 - deleting
+	 - renaming
+	 - reparenting
+	 - Note: In case of an running upload for a file it's only allowed to read the item. To prevent additional modifications.
+
+	 */
+	func getPermissions(for item: ItemMetadata, at domain: NSFileProviderDomainIdentifier) -> NSFileProviderItemCapabilities
+
+	func getPermissionsForRootItem(at domain: NSFileProviderDomainIdentifier?) -> NSFileProviderItemCapabilities
+}
+
+private enum PermissionProviderKey: DependencyKey {
+	static let liveValue: PermissionProvider = PermissionProviderImpl()
+	#if DEBUG
+	static let testValue: PermissionProvider = UnimplementedPermissionProvider()
+	#endif
+}
+
+extension DependencyValues {
+	var permissionProvider: PermissionProvider {
+		get { self[PermissionProviderKey.self] }
+		set { self[PermissionProviderKey.self] = newValue }
+	}
+}
+
+struct PermissionProviderImpl: PermissionProvider {
+	@Dependency(\.fullVersionChecker) private var fullVersionChecker
+	@Dependency(\.hubRepository) private var hubRepository
+
+	func getPermissions(for item: ItemMetadata, at domain: NSFileProviderDomainIdentifier) -> NSFileProviderItemCapabilities {
+		if item.statusCode == .uploadError {
+			return .allowsDeleting
+		}
+
+		let vaultID = domain.rawValue
+		let hubSubscriptionState: HubSubscriptionState?
+		do {
+			let hubVault = try hubRepository.getHubVault(vaultID: vaultID)
+			hubSubscriptionState = hubVault?.subscriptionState
+		} catch {
+			hubSubscriptionState = nil
+			DDLogError("Failed to retrieve possible hub vault for with id: \(vaultID)")
+		}
+
+		if !fullVersionChecker.isFullVersion && hubSubscriptionState != .active {
+			return FileProviderItem.readOnlyCapabilities
+		}
+		if item.type == .folder {
+			return [.allowsAddingSubItems, .allowsContentEnumerating, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting]
+		}
+		if item.statusCode == .isUploading {
+			return FileProviderItem.readOnlyCapabilities
+		}
+		return [.allowsWriting, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting]
+	}
+
+	func getPermissionsForRootItem(at domain: NSFileProviderDomainIdentifier?) -> NSFileProviderItemCapabilities {
+		if fullVersionChecker.isFullVersion {
+			return [.allowsAll]
+		}
+		guard let domain else {
+			return FileProviderItem.readOnlyCapabilities
+		}
+		let vaultID = domain.rawValue
+		let hubSubscriptionState: HubSubscriptionState?
+		do {
+			let hubVault = try hubRepository.getHubVault(vaultID: vaultID)
+			hubSubscriptionState = hubVault?.subscriptionState
+		} catch {
+			hubSubscriptionState = nil
+			DDLogError("Failed to retrieve possible hub vault for with id: \(vaultID)")
+		}
+		switch hubSubscriptionState {
+		case .active:
+			return [.allowsAll]
+		case .inactive, nil:
+			return FileProviderItem.readOnlyCapabilities
+		}
+	}
+}
+
+#if DEBUG
+struct UnimplementedPermissionProvider: PermissionProvider {
+	func getPermissions(for item: ItemMetadata, at domain: NSFileProviderDomainIdentifier) -> NSFileProviderItemCapabilities {
+		unimplemented("\(Self.self).getPermissions", placeholder: .allowsReading)
+	}
+
+	func getPermissionsForRootItem(at domain: NSFileProviderDomainIdentifier?) -> NSFileProviderItemCapabilities {
+		unimplemented("\(Self.self).getPermissionsForRootItem", placeholder: .allowsReading)
+	}
+}
+#endif
diff --git a/CryptomatorFileProvider/RootFileProviderItem.swift b/CryptomatorFileProvider/RootFileProviderItem.swift
index c46984e8b..fafb4dafb 100644
--- a/CryptomatorFileProvider/RootFileProviderItem.swift
+++ b/CryptomatorFileProvider/RootFileProviderItem.swift
@@ -19,12 +19,13 @@ public class RootFileProviderItem: NSObject, NSFileProviderItem {
 	public let typeIdentifier = kUTTypeFolder as String
 	public let documentSize: NSNumber? = nil
 	public var capabilities: NSFileProviderItemCapabilities {
-		if fullVersionChecker.isFullVersion {
-			return [.allowsAll]
-		} else {
-			return FileProviderItem.readOnlyCapabilities
-		}
+		return permissionProvider.getPermissionsForRootItem(at: domain?.identifier)
 	}
 
-	@Dependency(\.fullVersionChecker) private var fullVersionChecker
+	private let domain: NSFileProviderDomain?
+	@Dependency(\.permissionProvider) private var permissionProvider
+
+	public init(domain: NSFileProviderDomain?) {
+		self.domain = domain
+	}
 }
diff --git a/CryptomatorFileProvider/Workflow/WorkflowFactory.swift b/CryptomatorFileProvider/Workflow/WorkflowFactory.swift
index 2ebe1387e..17a92c359 100644
--- a/CryptomatorFileProvider/Workflow/WorkflowFactory.swift
+++ b/CryptomatorFileProvider/Workflow/WorkflowFactory.swift
@@ -7,6 +7,7 @@
 //
 
 import CryptomatorCloudAccessCore
+import Dependencies
 import FileProvider
 import Foundation
 
@@ -21,6 +22,7 @@ struct WorkflowFactory {
 	let downloadTaskManager: DownloadTaskManager
 	let dependencyFactory = WorkflowDependencyFactory()
 	let domainIdentifier: NSFileProviderDomainIdentifier
+	@Dependency(\.permissionProvider) private var permissionProvider
 
 	func createWorkflow(for deletionTask: DeletionTask) -> Workflow<Void> {
 		let taskExecutor = DeletionTaskExecutor(provider: provider, itemMetadataManager: itemMetadataManager)
diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterEnumerateItemTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterEnumerateItemTests.swift
index a7882b36f..ca98e991a 100644
--- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterEnumerateItemTests.swift
+++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterEnumerateItemTests.swift
@@ -9,6 +9,7 @@
 import CryptomatorCloudAccessCore
 import XCTest
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class FileProviderAdapterEnumerateItemTests: FileProviderAdapterTestCase {
 	override func setUpWithError() throws {
@@ -34,6 +35,9 @@ class FileProviderAdapterEnumerateItemTests: FileProviderAdapterTestCase {
 			ItemMetadata(id: 3, name: "TestFolder", type: .file, size: nil, parentID: 4, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/Foo/TestFolder"), isPlaceholderItem: false, isCandidateForCacheCleanup: false, favoriteRank: 1, tagData: nil)
 		]
 		metadataManagerMock.workingSetMetadata = mockMetadata
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 		let expectation = XCTestExpectation()
 		adapter.enumerateItems(for: .workingSet, withPageToken: nil).then { itemList in
 			XCTAssertEqual(mockMetadata.map { FileProviderItem(metadata: $0, domainIdentifier: .test) }, itemList.items)
diff --git a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift
index 2c83892f7..e2f4fb8cd 100644
--- a/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift
+++ b/CryptomatorFileProviderTests/FileProviderAdapter/FileProviderAdapterImportDocumentTests.swift
@@ -12,6 +12,7 @@ import Promises
 import XCTest
 @testable import CryptomatorCommonCore
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class FileProviderAdapterImportDocumentTests: FileProviderAdapterTestCase {
 	let itemID: Int64 = 2
@@ -26,6 +27,9 @@ class FileProviderAdapterImportDocumentTests: FileProviderAdapterTestCase {
 	// MARK: LocalItemImport
 
 	func testLocalItemImport() throws {
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 		let fileURL = tmpDirectory.appendingPathComponent("ItemToBeImported.txt", isDirectory: false)
 		let fileContent = "TestContent"
 		try fileContent.write(to: fileURL, atomically: true, encoding: .utf8)
diff --git a/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift b/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift
index 131daf148..912b7bc19 100644
--- a/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift
+++ b/CryptomatorFileProviderTests/FileProviderEnumeratorTests.swift
@@ -13,6 +13,7 @@ import Promises
 import XCTest
 @testable import CryptomatorCommonCore
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class FileProviderEnumeratorTestCase: XCTestCase {
 	var enumerationObserverMock: NSFileProviderEnumerationObserverMock!
@@ -50,6 +51,10 @@ class FileProviderEnumeratorTestCase: XCTestCase {
 	}
 
 	func assertChangeObserverUpdated(deletedItems: [NSFileProviderItemIdentifier], updatedItems: [FileProviderItem], currentSyncAnchor: NSFileProviderSyncAnchor) {
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
+
 		XCTAssertEqual([deletedItems], changeObserverMock.didDeleteItemsWithIdentifiersReceivedInvocations)
 		let receivedUpdatedItems = changeObserverMock.didUpdateReceivedInvocations as? [[FileProviderItem]]
 		XCTAssertEqual([updatedItems], receivedUpdatedItems)
@@ -179,6 +184,10 @@ class FileProviderEnumeratorTests: FileProviderEnumeratorTestCase {
 	}
 
 	private func assertEnumerateItemObserverSucceeded(itemList: FileProviderItemList) {
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
+
 		XCTAssertEqual([itemList.nextPageToken], enumerationObserverMock.finishEnumeratingUpToReceivedInvocations)
 		let receivedInvocations = enumerationObserverMock.didEnumerateReceivedInvocations as? [[FileProviderItem]]
 		XCTAssertEqual([items], receivedInvocations)
diff --git a/CryptomatorFileProviderTests/FileProviderItemTests.swift b/CryptomatorFileProviderTests/FileProviderItemTests.swift
index 1c4af1510..e8829ef6c 100644
--- a/CryptomatorFileProviderTests/FileProviderItemTests.swift
+++ b/CryptomatorFileProviderTests/FileProviderItemTests.swift
@@ -108,59 +108,19 @@ class FileProviderItemTests: XCTestCase {
 
 	// MARK: Capabilities
 
-	func testUploadingItemRestrictsCapabilityToRead() {
-		let fullVersionCheckerMock = FullVersionCheckerMock()
-		fullVersionCheckerMock.isFullVersion = true
-		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
+	func testCapabilitiesArePassedThroughFromPermissionProvider() {
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
 
 		let cloudPath = CloudPath("/test.txt")
 		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
 		let item = FileProviderItem(metadata: metadata, domainIdentifier: .test)
-		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, item.capabilities)
-	}
-
-	func testUploadingFolderDoesNotRestrictCapabilities() {
-		let fullVersionCheckerMock = FullVersionCheckerMock()
-		fullVersionCheckerMock.isFullVersion = true
-		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
-
-		let cloudPath = CloudPath("/test")
-		let metadata = ItemMetadata(id: 2, name: "test", type: .folder, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
-		let item = FileProviderItem(metadata: metadata, domainIdentifier: .test)
-		XCTAssertEqual([.allowsAddingSubItems, .allowsContentEnumerating, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting], item.capabilities)
-	}
-
-	func testCapabilitiesForRestrictedVersion() {
-		let fullVersionCheckerMock = FullVersionCheckerMock()
-		fullVersionCheckerMock.isFullVersion = false
-		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
-
-		let cloudPath = CloudPath("/test.txt")
-		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
-		let item = FileProviderItem(metadata: metadata, domainIdentifier: .test)
-		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, item.capabilities)
-	}
-
-	func testFailedUploadItemCapabilitiesForRestrictedVersion() {
-		let fullVersionCheckerMock = FullVersionCheckerMock()
-		fullVersionCheckerMock.isFullVersion = false
-		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
 
-		let cloudPath = CloudPath("/test.txt")
-		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .uploadError, cloudPath: cloudPath, isPlaceholderItem: false)
-		let item = FileProviderItem(metadata: metadata, domainIdentifier: .test)
-		XCTAssertEqual(NSFileProviderItemCapabilities.allowsDeleting, item.capabilities)
-	}
-
-	func testFailedUploadFolderCapabilitiesForRestrictedVersion() {
-		let fullVersionCheckerMock = FullVersionCheckerMock()
-		fullVersionCheckerMock.isFullVersion = false
-		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
-
-		let cloudPath = CloudPath("/test")
-		let metadata = ItemMetadata(id: 2, name: "test", type: .folder, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .uploadError, cloudPath: cloudPath, isPlaceholderItem: false)
-		let item = FileProviderItem(metadata: metadata, domainIdentifier: .test)
-		XCTAssertEqual(NSFileProviderItemCapabilities.allowsDeleting, item.capabilities)
+		let capabilities: [NSFileProviderItemCapabilities] = [.allowsAddingSubItems, .allowsContentEnumerating, .allowsDeleting, .allowsReading, .allowsReparenting, .allowsWriting]
+		for capability in capabilities {
+			permissionProviderMock.getPermissionsForAtReturnValue = capability
+			XCTAssertEqual(capability, item.capabilities)
+		}
 	}
 
 	// MARK: Evict File From Cache Action
diff --git a/CryptomatorFileProviderTests/FileProviderNotificatorTests.swift b/CryptomatorFileProviderTests/FileProviderNotificatorTests.swift
index 650d54507..4e54026a2 100644
--- a/CryptomatorFileProviderTests/FileProviderNotificatorTests.swift
+++ b/CryptomatorFileProviderTests/FileProviderNotificatorTests.swift
@@ -9,6 +9,7 @@
 import CryptomatorCloudAccessCore
 import XCTest
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 @available(iOS 14.0, *)
 class FileProviderNotificatorTests: XCTestCase {
@@ -97,6 +98,11 @@ class FileProviderNotificatorTests: XCTestCase {
 		})
 
 		let actualItems = notificator.popUpdateContainerItems() as? [FileProviderItem]
+
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
+
 		XCTAssertEqual([updatedItem], actualItems?.sorted())
 		XCTAssert(notificator.popUpdateWorkingSetItems().isEmpty)
 		XCTAssert(notificator.getItemIdentifiersToDeleteFromWorkingSet().isEmpty)
@@ -109,6 +115,9 @@ class FileProviderNotificatorTests: XCTestCase {
 	}
 
 	private func assertUpdateWorkingSetHasUpdatedItems() {
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 		let actualItems = notificator.popUpdateWorkingSetItems() as? [FileProviderItem]
 		XCTAssertEqual(updatedItems.sorted(), actualItems?.sorted())
 	}
diff --git a/CryptomatorFileProviderTests/Middleware/TaskExecutor/ItemEnumerationTaskTests.swift b/CryptomatorFileProviderTests/Middleware/TaskExecutor/ItemEnumerationTaskTests.swift
index 05dc2be97..1c4ca9b14 100644
--- a/CryptomatorFileProviderTests/Middleware/TaskExecutor/ItemEnumerationTaskTests.swift
+++ b/CryptomatorFileProviderTests/Middleware/TaskExecutor/ItemEnumerationTaskTests.swift
@@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore
 import Promises
 import XCTest
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class ItemEnumerationTaskTests: CloudTaskExecutorTestCase {
 	override func setUpWithError() throws {
@@ -201,6 +202,7 @@ class ItemEnumerationTaskTests: CloudTaskExecutorTestCase {
 
 	// MARK: Folder
 
+	// swiftlint:disable:next function_body_length
 	func testFolderEnumeration() throws {
 		let expectation = XCTestExpectation(description: "Folder Enumeration")
 
@@ -222,6 +224,9 @@ class ItemEnumerationTaskTests: CloudTaskExecutorTestCase {
 		let expectedSubFolderFileProviderItems = expectedItemMetadataInsideSubFolder.map { FileProviderItem(metadata: $0, domainIdentifier: .test) }
 
 		let taskExecutor = ItemEnumerationTaskExecutor(domainIdentifier: .test, provider: cloudProviderMock, itemMetadataManager: metadataManagerMock, cachedFileManager: cachedFileManagerMock, uploadTaskManager: uploadTaskManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, deleteItemHelper: deleteItemHelper)
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 
 		taskExecutor.execute(task: enumerationTask).then { fileProviderItemList -> FileProviderItem in
 			XCTAssertEqual(5, fileProviderItemList.items.count)
@@ -283,6 +288,10 @@ class ItemEnumerationTaskTests: CloudTaskExecutorTestCase {
 		                                                  FileProviderItem(metadata: ItemMetadata(id: 6, name: "File 4", type: .file, size: 14, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/File 4"), isPlaceholderItem: false), domainIdentifier: .test),
 		                                                  FileProviderItem(metadata: ItemMetadata(id: 7, name: "NewFileFromCloud", type: .file, size: 24, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: CloudPath("/NewFileFromCloud"), isPlaceholderItem: false), domainIdentifier: .test)]
 
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
+
 		let taskExecutor = ItemEnumerationTaskExecutor(domainIdentifier: .test, provider: cloudProviderMock, itemMetadataManager: metadataManagerMock, cachedFileManager: cachedFileManagerMock, uploadTaskManager: uploadTaskManagerMock, reparentTaskManager: reparentTaskManagerMock, deletionTaskManager: deletionTaskManagerMock, itemEnumerationTaskManager: itemEnumerationTaskManagerMock, deleteItemHelper: deleteItemHelper)
 
 		taskExecutor.execute(task: enumerationTask).then { fileProviderItemList -> Promise<FileProviderItemList> in
diff --git a/CryptomatorFileProviderTests/Mocks/PermissionProviderMock.swift b/CryptomatorFileProviderTests/Mocks/PermissionProviderMock.swift
new file mode 100644
index 000000000..7571ceee7
--- /dev/null
+++ b/CryptomatorFileProviderTests/Mocks/PermissionProviderMock.swift
@@ -0,0 +1,51 @@
+//
+//  PermissionProviderMock.swift
+//  CryptomatorFileProviderTests
+//
+//  Created by Philipp Schmid on 19.09.23.
+//  Copyright © 2023 Skymatic GmbH. All rights reserved.
+//
+
+import CryptomatorFileProvider
+import FileProvider
+import Foundation
+
+final class PermissionProviderMock: PermissionProvider {
+	// MARK: - getPermissions
+
+	var getPermissionsForAtCallsCount = 0
+	var getPermissionsForAtCalled: Bool {
+		getPermissionsForAtCallsCount > 0
+	}
+
+	var getPermissionsForAtReceivedArguments: (item: ItemMetadata, domain: NSFileProviderDomainIdentifier)?
+	var getPermissionsForAtReceivedInvocations: [(item: ItemMetadata, domain: NSFileProviderDomainIdentifier)] = []
+	var getPermissionsForAtReturnValue: NSFileProviderItemCapabilities!
+	var getPermissionsForAtClosure: ((ItemMetadata, NSFileProviderDomainIdentifier) -> NSFileProviderItemCapabilities)?
+
+	func getPermissions(for item: ItemMetadata, at domain: NSFileProviderDomainIdentifier) -> NSFileProviderItemCapabilities {
+		getPermissionsForAtCallsCount += 1
+		getPermissionsForAtReceivedArguments = (item: item, domain: domain)
+		getPermissionsForAtReceivedInvocations.append((item: item, domain: domain))
+		return getPermissionsForAtClosure.map({ $0(item, domain) }) ?? getPermissionsForAtReturnValue
+	}
+
+	// MARK: - getPermissionsForRootItem
+
+	var getPermissionsForRootItemAtCallsCount = 0
+	var getPermissionsForRootItemAtCalled: Bool {
+		getPermissionsForRootItemAtCallsCount > 0
+	}
+
+	var getPermissionsForRootItemAtReceivedDomain: NSFileProviderDomainIdentifier?
+	var getPermissionsForRootItemAtReceivedInvocations: [NSFileProviderDomainIdentifier?] = []
+	var getPermissionsForRootItemAtReturnValue: NSFileProviderItemCapabilities!
+	var getPermissionsForRootItemAtClosure: ((NSFileProviderDomainIdentifier?) -> NSFileProviderItemCapabilities)?
+
+	func getPermissionsForRootItem(at domain: NSFileProviderDomainIdentifier?) -> NSFileProviderItemCapabilities {
+		getPermissionsForRootItemAtCallsCount += 1
+		getPermissionsForRootItemAtReceivedDomain = domain
+		getPermissionsForRootItemAtReceivedInvocations.append(domain)
+		return getPermissionsForRootItemAtClosure.map({ $0(domain) }) ?? getPermissionsForRootItemAtReturnValue
+	}
+}
diff --git a/CryptomatorFileProviderTests/PermissionProviderImplTests.swift b/CryptomatorFileProviderTests/PermissionProviderImplTests.swift
new file mode 100644
index 000000000..67fdb5a8e
--- /dev/null
+++ b/CryptomatorFileProviderTests/PermissionProviderImplTests.swift
@@ -0,0 +1,137 @@
+//
+//  PermissionProviderImplTests.swift
+//  CryptomatorFileProviderTests
+//
+//  Created by Philipp Schmid on 19.09.23.
+//  Copyright © 2023 Skymatic GmbH. All rights reserved.
+//
+
+import CryptomatorCloudAccessCore
+import XCTest
+@testable import CryptomatorCommonCore
+@testable import CryptomatorFileProvider
+@testable import Dependencies
+
+final class PermissionProviderImplTests: XCTestCase {
+	private static let defaultFolderCapabilities: NSFileProviderItemCapabilities = [.allowsAddingSubItems, .allowsContentEnumerating, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting]
+	private var fullVersionCheckerMock: FullVersionCheckerMock!
+	private var hubRepositoryMock: HubRepositoryMock!
+	private var permissionProvider: PermissionProviderImpl!
+
+	override func setUpWithError() throws {
+		fullVersionCheckerMock = FullVersionCheckerMock()
+		hubRepositoryMock = HubRepositoryMock()
+		DependencyValues.mockDependency(\.hubRepository, with: hubRepositoryMock)
+		DependencyValues.mockDependency(\.fullVersionChecker, with: fullVersionCheckerMock)
+		permissionProvider = PermissionProviderImpl()
+	}
+
+	// MARK: Full Version
+
+	func testUploadingItemRestrictsCapabilityToRead() {
+		fullVersionCheckerMock.isFullVersion = true
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, actualCapabilities)
+	}
+
+	func testUploadingFolderDoesNotRestrictCapabilities() {
+		fullVersionCheckerMock.isFullVersion = true
+
+		let cloudPath = CloudPath("/test")
+		let metadata = ItemMetadata(id: 2, name: "test", type: .folder, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(Self.defaultFolderCapabilities, actualCapabilities)
+	}
+
+	func testCapabilitiesForRestrictedVersion() {
+		fullVersionCheckerMock.isFullVersion = false
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, actualCapabilities)
+	}
+
+	func testFailedUploadItemCapabilitiesForRestrictedVersion() {
+		fullVersionCheckerMock.isFullVersion = false
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .uploadError, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsDeleting, actualCapabilities)
+	}
+
+	func testFailedUploadFolderCapabilitiesForRestrictedVersion() {
+		fullVersionCheckerMock.isFullVersion = false
+
+		let cloudPath = CloudPath("/test")
+		let metadata = ItemMetadata(id: 2, name: "test", type: .folder, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .uploadError, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsDeleting, actualCapabilities)
+	}
+
+	func testFullVersionNoActiveHubScriptionReturnsFullPermissionsForFile() {
+		fullVersionCheckerMock.isFullVersion = true
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .inactive)
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual([.allowsWriting, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting], actualCapabilities)
+	}
+
+	// MARK: Cryptomator Hub
+
+	func testUploadingItemRestrictsCapabilityToReadWithActiveHubSubscription() {
+		fullVersionCheckerMock.isFullVersion = false
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .active)
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, actualCapabilities)
+	}
+
+	func testNoFullVersionNoActiveHubSubscriptionRestrictsToReadOnly() {
+		fullVersionCheckerMock.isFullVersion = false
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .inactive)
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(NSFileProviderItemCapabilities.allowsReading, actualCapabilities)
+	}
+
+	func testFolderCapabilitiesNoFullVersionActiveHubSubscription() {
+		fullVersionCheckerMock.isFullVersion = false
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .active)
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .folder, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(Self.defaultFolderCapabilities, actualCapabilities)
+	}
+
+	func testUploadingFolderDoesNotRestrictCapabilitiesForActiveHubSubsription() {
+		fullVersionCheckerMock.isFullVersion = false
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .active)
+
+		let cloudPath = CloudPath("/test")
+		let metadata = ItemMetadata(id: 2, name: "test", type: .folder, size: nil, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploading, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual(Self.defaultFolderCapabilities, actualCapabilities)
+	}
+
+	func testNoFullVersionActiveHubScriptionReturnsFullPermissionsForFile() {
+		fullVersionCheckerMock.isFullVersion = false
+		hubRepositoryMock.getHubVaultVaultIDReturnValue = .init(vaultUID: "12345", subscriptionState: .active)
+
+		let cloudPath = CloudPath("/test.txt")
+		let metadata = ItemMetadata(id: 2, name: "test.txt", type: .file, size: 100, parentID: NSFileProviderItemIdentifier.rootContainerDatabaseValue, lastModifiedDate: nil, statusCode: .isUploaded, cloudPath: cloudPath, isPlaceholderItem: false)
+		let actualCapabilities = permissionProvider.getPermissions(for: metadata, at: .test)
+		XCTAssertEqual([.allowsWriting, .allowsReading, .allowsDeleting, .allowsRenaming, .allowsReparenting], actualCapabilities)
+	}
+}
diff --git a/CryptomatorFileProviderTests/ServiceSource/CacheManagingServiceSourceTests.swift b/CryptomatorFileProviderTests/ServiceSource/CacheManagingServiceSourceTests.swift
index 2772c4de2..eee1b96de 100644
--- a/CryptomatorFileProviderTests/ServiceSource/CacheManagingServiceSourceTests.swift
+++ b/CryptomatorFileProviderTests/ServiceSource/CacheManagingServiceSourceTests.swift
@@ -11,6 +11,7 @@ import Promises
 import XCTest
 @testable import CryptomatorCommonCore
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class CacheManagingServiceSourceTests: XCTestCase {
 	var serviceSource: CacheManagingServiceSource!
@@ -57,6 +58,9 @@ class CacheManagingServiceSourceTests: XCTestCase {
 		let expectation = XCTestExpectation()
 		let cacheManagerMock = CachedFileManagerMock()
 		cacheManagerFactoryMock.createCachedFileManagerForReturnValue = cacheManagerMock
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 		let domainIdentifier = NSFileProviderDomainIdentifier("Test-Domain")
 		let itemID: Int64 = 2
 		let itemIdentifier = NSFileProviderItemIdentifier(domainIdentifier: domainIdentifier, itemID: itemID)
diff --git a/CryptomatorFileProviderTests/WorkingSetObserverTests.swift b/CryptomatorFileProviderTests/WorkingSetObserverTests.swift
index 034a0bca1..728b31357 100644
--- a/CryptomatorFileProviderTests/WorkingSetObserverTests.swift
+++ b/CryptomatorFileProviderTests/WorkingSetObserverTests.swift
@@ -10,6 +10,7 @@ import CryptomatorCloudAccessCore
 import GRDB
 import XCTest
 @testable import CryptomatorFileProvider
+@testable import Dependencies
 
 class WorkingSetObserverTests: XCTestCase {
 	var observer: WorkingSetObserver!
@@ -31,6 +32,9 @@ class WorkingSetObserverTests: XCTestCase {
 
 		XCTAssertEqual(1, notificatorMock.updateWorkingSetItemsCallsCount)
 		let actualUpdatedItems = notificatorMock.updateWorkingSetItemsReceivedItems as? [FileProviderItem]
+		let permissionProviderMock = PermissionProviderMock()
+		DependencyValues.mockDependency(\.permissionProvider, with: permissionProviderMock)
+		permissionProviderMock.getPermissionsForAtReturnValue = .allowsReading
 		XCTAssertEqual(updatedItems.sorted(), actualUpdatedItems?.sorted())
 		XCTAssertEqual(1, notificatorMock.refreshWorkingSetCallsCount)
 	}
diff --git a/FileProviderExtension/FileProviderExtension.swift b/FileProviderExtension/FileProviderExtension.swift
index ee8f385b9..2458a4f48 100644
--- a/FileProviderExtension/FileProviderExtension.swift
+++ b/FileProviderExtension/FileProviderExtension.swift
@@ -69,7 +69,7 @@ class FileProviderExtension: NSFileProviderExtension {
 		// resolve the given identifier to a record in the model
 		DDLogDebug("FPExt: item(for: \(identifier)) called")
 		if identifier == .rootContainer || identifier.rawValue == "File Provider Storage" || identifier.rawValue == domain?.identifier.rawValue {
-			return RootFileProviderItem()
+			return RootFileProviderItem(domain: domain)
 		}
 		let adapter = try getAdapterWithWrappedError()
 		return try adapter.item(for: identifier)