field_ios

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

commit 6713e02acef3a9eff59b8591d2ab212a2a8c5b98
parent d34c612ea36cd7e1530d67675236669c897460f9
Author: triesap <tyson@radroots.org>
Date:   Sun,  9 Aug 2026 19:48:45 +0000

ios: expose physical SSRF qualification failure

- Surface stable runtime failure codes in the status view.
- Exercise strict public Blossom endpoint rejection through app startup.
- Automate identity and reconfiguration transitions in qualification UI tests.
- Assert private loopback origins fail closed before the app shell appears.

Diffstat:
MRadroots/Views/RuntimeStatusView.swift | 7+++++++
MRadrootsUITests/RadrootsRemoteQualificationUITests.swift | 37+++++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+), 0 deletions(-)

diff --git a/Radroots/Views/RuntimeStatusView.swift b/Radroots/Views/RuntimeStatusView.swift @@ -22,6 +22,12 @@ struct RuntimeStatusView: View { .font(.body) .foregroundStyle(.secondary) .multilineTextAlignment(.center) + if case let .failed(failure) = phase { + Text("Error code \(failure.code)") + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + .accessibilityIdentifier("radroots.runtime.failure_code") + } if case .identityRequired = phase { Button("Create identity", action: createIdentity) .buttonStyle(.borderedProminent) @@ -118,4 +124,5 @@ struct RuntimeStatusView: View { "Your durable local work is safe." } } + } diff --git a/RadrootsUITests/RadrootsRemoteQualificationUITests.swift b/RadrootsUITests/RadrootsRemoteQualificationUITests.swift @@ -21,6 +21,43 @@ final class RadrootsRemoteQualificationUITests: XCTestCase { } @MainActor + func testStrictPublicEndpointRejectsPrivateAddress() throws { + let environment = try QualificationConfiguration.environment() + let configuration = QualificationConfiguration( + runID: environment.runID, + relayURLs: [], + blossomOrigins: ["https://127.0.0.1"] + ) + let app = XCUIApplication() + app.launchEnvironment = configuration.launchEnvironment + app.launch() + + let failureCode = app.descendants(matching: .any)["radroots.runtime.failure_code"] + for _ in 0 ..< 8 where !failureCode.exists { + for identifier in [ + "radroots.identity.create", + "radroots.identity.unlock", + "radroots.configuration.reconfigure", + "radroots.identity.recover", + ] { + let action = app.descendants(matching: .any)[identifier] + if action.exists, action.isHittable { + action.tap() + break + } + } + _ = failureCode.waitForExistence(timeout: 4) + } + XCTAssertEqual( + failureCode.label, + "Error code runtime_failed", + "A private Blossom endpoint did not expose the typed fail-closed result; " + + "failure_code.label=\(failureCode.exists ? failureCode.label : "missing")" + ) + XCTAssertFalse(app.tabBars.firstMatch.exists) + } + + @MainActor func testRemoteBlossomUploadAndRecovery() throws { let configuration = try QualificationConfiguration.environment() let app = launchToRoot(configuration)