field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | LICENSE

commit 44ea2b2d22d3bf1b4a982adbb796fef10a68464f
parent 70fb20459654f4d687f5227ac730718395dc3a4c
Author: triesap <tyson@radroots.org>
Date:   Thu, 18 Jun 2026 13:59:05 -0700

security: prove identity policy config

- expose a debug-only identity policy probe
- map configured custody policy to secure store policy values
- keep production user-presence policy on compiled config
- include the generated app project source reference

Diffstat:
MRadroots.xcodeproj/project.pbxproj | 4++++
MRadroots/App/AppEntry.swift | 7+++++++
MRadroots/App/AppState.swift | 4++++
ARadroots/Runtime/FieldIdentityPolicyUITestProbe.swift | 35+++++++++++++++++++++++++++++++++++
4 files changed, 50 insertions(+), 0 deletions(-)

diff --git a/Radroots.xcodeproj/project.pbxproj b/Radroots.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 4B44B723FF06ECC363A486BA /* TradeListingDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BAE32CD1D46033DDA1A5BB /* TradeListingDetailView.swift */; }; 505A5731ACDBBB0296134340 /* TradeListingCreateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947ADA6D32E42ED2B40A5351 /* TradeListingCreateView.swift */; }; 5AECD474FB2F91855BDD79C0 /* PostFeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F21554DA87EEC1E5C5F38365 /* PostFeedViewModel.swift */; }; + 5FB3E3E450EE7DFF30F3A005 /* FieldIdentityPolicyUITestProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E150C6C18B2A06F2F3227C6 /* FieldIdentityPolicyUITestProbe.swift */; }; 657BEA5AAFF129E10177FE63 /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63189EB90A86A9929BECD9ED /* Nostr.swift */; }; 6E15D30653861F26AC45B501 /* FieldLocalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB0A2CFCBBFA9D204C6992B /* FieldLocalState.swift */; }; 7C8DD424F3E3E0AB1B133863 /* RadrootsKitBindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC881872F750120A184F45E6 /* RadrootsKitBindings.swift */; }; @@ -81,6 +82,7 @@ 466BFA2F60BE3113EDD1BA3B /* TradeOrderRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeOrderRequestView.swift; sourceTree = "<group>"; }; 491D57E540BAB04F24619737 /* FieldFileAccessUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldFileAccessUITestProbe.swift; sourceTree = "<group>"; }; 4BC4B7D0BB4C6D8E4B0AA4AD /* radroots.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = radroots.xcconfig; sourceTree = "<group>"; }; + 4E150C6C18B2A06F2F3227C6 /* FieldIdentityPolicyUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldIdentityPolicyUITestProbe.swift; sourceTree = "<group>"; }; 54EE5A34FE2086899F5B7568 /* radroots.git.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = radroots.git.xcconfig; sourceTree = "<group>"; }; 59AC0543EF8335D691D56BD3 /* FieldBackgroundExecutionUITestProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldBackgroundExecutionUITestProbe.swift; sourceTree = "<group>"; }; 63189EB90A86A9929BECD9ED /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; }; @@ -209,6 +211,7 @@ EF05B944D348DAA4EF28B463 /* FieldDocumentInterchangeUITestProbe.swift */, 3E6187FA7C4786EC662718B2 /* FieldExternalActions.swift */, 491D57E540BAB04F24619737 /* FieldFileAccessUITestProbe.swift */, + 4E150C6C18B2A06F2F3227C6 /* FieldIdentityPolicyUITestProbe.swift */, CA942715DB13FFD494FD35A0 /* FieldIdentityPublicMetadataStore.swift */, 9EB0A2CFCBBFA9D204C6992B /* FieldLocalState.swift */, 2F3541389124A31F5D701A45 /* FieldLocationCheckIn.swift */, @@ -448,6 +451,7 @@ E432FD39ECC8F03764EEED81 /* FieldDocumentInterchangeUITestProbe.swift in Sources */, 82903551F5E15FBDAC388D20 /* FieldExternalActions.swift in Sources */, 7E650F6DA30931E310F842E2 /* FieldFileAccessUITestProbe.swift in Sources */, + 5FB3E3E450EE7DFF30F3A005 /* FieldIdentityPolicyUITestProbe.swift in Sources */, D4CFDE54747B6D6957977025 /* FieldIdentityPublicMetadataStore.swift in Sources */, 6E15D30653861F26AC45B501 /* FieldLocalState.swift in Sources */, 1C6EA551530A46CA77BD9E1C /* FieldLocationCheckIn.swift in Sources */, diff --git a/Radroots/App/AppEntry.swift b/Radroots/App/AppEntry.swift @@ -44,6 +44,13 @@ public struct AppEntry<Main: View>: View { .accessibilityIdentifier("field_ios.document_interchange.probe") .accessibilityValue(probeValue) } + if let probeValue = appState.identityPolicyProbeValue { + Color.clear + .frame(width: 1, height: 1) + .accessibilityElement() + .accessibilityIdentifier("field_ios.identity_policy.probe") + .accessibilityValue(probeValue) + } if let probeValue = appState.telemetryProbeValue { Color.clear .frame(width: 1, height: 1) diff --git a/Radroots/App/AppState.swift b/Radroots/App/AppState.swift @@ -45,6 +45,7 @@ public final class AppState: ObservableObject { @Published public private(set) var relaySettingsSourceLabel: String = RelaySettingsSource.buildConfig.displayName @Published public private(set) var fileAccessProbeValue: String? @Published public private(set) var documentInterchangeProbeValue: String? + @Published public private(set) var identityPolicyProbeValue: String? @Published public private(set) var telemetryProbeValue: String? @Published public private(set) var backgroundExecutionProbeValue: String? @Published public private(set) var externalActionStatus: String? @@ -116,6 +117,9 @@ public final class AppState: ObservableObject { let service = try radroots.start(telemetry: telemetry) let secureStore = try FieldSecureIdentityStore.configured() let metadataStore = try FieldIdentityPublicMetadataStore.configured() + #if DEBUG + identityPolicyProbeValue = try FieldIdentityPolicyUITestProbe.value() + #endif let appBundleIdentifier = try bundleIdentifier() let resetLocalStateRequested = BuildConfig.bool(.resetLocalState) == true let backgroundExecution = try FieldBackgroundExecution.configured( diff --git a/Radroots/Runtime/FieldIdentityPolicyUITestProbe.swift b/Radroots/Runtime/FieldIdentityPolicyUITestProbe.swift @@ -0,0 +1,35 @@ +#if DEBUG +import Foundation +import RadrootsKit + +enum FieldIdentityPolicyUITestProbe { + private static let enabledKey = "RADROOTS_FIELD_IOS_UI_TEST_IDENTITY_POLICY_PROBE" + + static var isRequested: Bool { + FieldUITestHarness.string(enabledKey) == "true" + } + + static func value() throws -> String? { + guard isRequested else { + return nil + } + let configuredPolicy = try FieldSecureIdentityStore.configuredAccessPolicy() + let storePolicy = configuredPolicy.storePolicy + return [ + "configured_policy=\(configuredPolicy.rawValue)", + "store_policy_accessibility=\(accessibilityValue(storePolicy.accessibility))", + "store_policy_device_local_only=\(storePolicy.deviceLocalOnly)", + "store_policy_user_presence_required=\(storePolicy.userPresenceRequired)" + ].joined(separator: ";") + } + + private static func accessibilityValue(_ accessibility: RadrootsSecretAccessibility) -> String { + switch accessibility { + case .whenUnlocked: + "when_unlocked" + case .afterFirstUnlock: + "after_first_unlock" + } + } +} +#endif