The Oversight mobile verifier is on TestFlight as of today. The repo is at
oversight-protocol/oversight-mobile,
the build that just landed for the internal beta is v0.1.11, and
the same Rust crates that power the desktop CLI are linked into the iOS and
Android binaries. There is exactly one verification implementation; a manifest
that opens on a laptop opens the same way on a phone, with the same answer.
The path to that build was not clean. I want to write down what shipped, why the architecture looks the way it does, and the single Apple-side detail that cost me six TestFlight builds I could see in App Store Connect but my testers could not. If you have ever uploaded to TestFlight, succeeded, and then watched the build never appear on a tester's phone, this post is the answer.
Why a phone-shaped surface
A provenance protocol whose only verifier is a Python CLI is a protocol nobody
verifies. The recipient of a sealed document is rarely the same person who
installed the issuer's tooling. They are a journalist who got handed a leaked
memo, an analyst checking whether the dataset they pulled is the dataset they
were told they pulled, an auditor confirming that a contract bundle has not
been tampered with since signing. They have a phone. They do not have
pip.
The mobile app does one thing: take an Oversight artifact (a hash, a QR code,
or a .oversight bundle), check it against the public Sigstore
Rekor log, and tell you whether the signature is valid, whether the inclusion
proof checks out, and what the manifest says about who issued it and to whom.
No accounts. No telemetry. No server in the middle. The cryptography happens
on the device, against a public append-only log that anybody can audit.
One core, two surfaces
The verifier is Flutter on top of Rust. The UI layer is Dart, single codebase,
both platforms. The verification core is the existing oversight-rust
workspace, embedded via flutter_rust_bridge
and called from Dart through a generated FFI shim. There is no second
implementation of Rekor proof verification, no second implementation of
manifest signature checks, no second copy of the synonym dictionary. Whatever
passes the desktop conformance suite passes the mobile build, because they
compile from the same source tree.
That choice has a cost. Cross-compiling Rust for iOS arm64, Android arm64, and Android x86_64 is a real pipeline. The repo carries cargo configurations for each target, a CI job that primes the toolchains on the runner, and a small wrapper crate that hides the platform-specific async runtime selection. The payoff is that I do not have to maintain two cryptography stacks. The most likely way a mobile verifier would lie to its user is by drifting out of sync with the canonical implementation; that class of bug is structurally impossible here.
iOS without a Mac
I do not own a Mac. The entire iOS pipeline runs on GitHub-hosted macOS
runners. flutter build ios --release --no-codesign assembles
the .app, xcodebuild archive signs it with a
Distribution certificate imported from a base64-encoded .p12 in
Actions secrets, and xcrun altool --upload-app ships the IPA to
App Store Connect with a key from the App Store Connect API. Nothing on my
machine ever touches an Xcode project file.
The interesting constraint is that the runner is headless. There is no
keychain, no Xcode project history, no developer account session. Every
secret has to arrive through the workflow's encrypted environment, and every
signing operation has to declare every input explicitly. The
apple-actions/import-codesign-certs and
apple-actions/download-provisioning-profiles actions handle the
keychain and profile setup; everything else is a shell script in the workflow
file. It is reproducible, it is auditable, and it does not require me to own
$2,500 of Apple hardware to ship.
The Info.plist key that hides every build
Here is the part that cost me a day. After v0.1.4 made it onto TestFlight and
a tester actually installed it, I shipped six more builds (v0.1.5
through v0.1.10) over the next several hours. Each one passed CI.
Each one returned UPLOAD SUCCEEDED with no errors from
altool. Each one received an "uploaded build had one or more
issues" email from App Store Connect that, on close reading, said delivery
was successful and the warning was advisory. None of them showed up on the
tester's phone.
The cause is a key called ITSAppUsesNonExemptEncryption. If your
Info.plist does not declare it, App Store Connect parks every
new build in a state called "Missing Compliance," which is invisible to
internal-testing groups by default. The build is uploaded. The build is
processed. The build sits there forever, gated behind a manual question that
asks whether your app uses non-exempt encryption.
Oversight's mobile verifier uses standard system cryptography (HTTPS, the platform's TLS stack, and the curated primitives in RustCrypto) and nothing in the regulated-export category. The honest answer is "no." Set the key once in the manifest:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
and every future build sails through compliance the moment it finishes processing, with no manual click and no human in the loop. v0.1.11 was the first build to ship with the key in place. App Store Connect processed it in about two minutes. It auto-attached itself to the internal testing group. It was on the tester's TestFlight app a few minutes after that, with a green check and no warning badge.
I am writing this down because the Apple documentation for the key is accurate but easy to miss, and the failure mode is silent. There is no error. There is no rejection email. The build simply does not appear, which makes the obvious next move ("upload another build") cost you another submission slot for nothing.
The April 28 SDK cutoff
The other thing the same v0.1.11 release fixed is the iOS 26 SDK migration.
Apple announced months ago that starting 2026-04-28, every new
upload has to be built with Xcode 26 or later, against the iOS 26 SDK. That
is two days from now. Builds against the iOS 18 SDK get a warning email
today and a hard rejection from Wednesday on. The fix on the GitHub Actions
side is one line: bump the runner image label from macos-15 to
macos-26, which has Xcode 26 preinstalled. The runtime image
was already in the GitHub-hosted runner pool; I had not realized I was still
pinned to the previous one until the ITMS-90725 warnings made it explicit.
What is in the build, and what is not
v0.1.11 is a pure verifier. It scans QR codes, accepts pasted hashes, and
opens .oversight bundles via the iOS share sheet. It pulls live
from Sigstore Rekor and checks inclusion proofs against the verified log
root. It shows the manifest fields: issuer, recipient, content hash, L3
mode, and the timestamp from RFC 3161. It does not store any of that
anywhere; the local history is encrypted at rest with a per-install key in
the platform keystore.
It does not sign. Hardware-backed signing — Secure Enclave on iOS, StrongBox on Android — is the v2 work, and it is gated on a real threat model for what kind of issuer a phone-bound key should be allowed to be. A device that can be confiscated at a border crossing is not the same trust anchor as an air-gapped laptop in a SCIF, and the protocol's policy primitives need to reflect that before a mobile signer ships.
What recruiters and operators care about
There is a slightly different audience for this post than for the L3 safety or SIEM export posts: people who evaluate the project as a whole rather than any one feature. For them, the short version is that Oversight is a cryptographic protocol with an end-to-end reference implementation, a conformance test suite, a federation spec, three SIEM-native exporters, a desktop GUI, and a mobile verifier. The mobile build is not a marketing skin. It is the same Rust verification core, on a different surface, with the platform-store distribution cleared end-to-end on a CI pipeline that does not require me to own a Mac. Everything is Apache 2.0, everything is in public repos, every release is a tag with a matching CI run.
Reproducible mobile builds are next. When that lands, you will be able to clone the repo, run one command, and confirm that the binary distributed by the App Store and Play Store is byte-identical to what you built locally. That is the point at which "trust the protocol" stops requiring "trust the store." Until then, the source is public and every artifact is traceable to a commit.