Review date: 2026-07-14
Reviewed tree: current 2026-07-14 worktree. A clean pre-public local signed candidate was verified separately, but its local evidence bundle is not tracked or published and must not be transferred to the rewritten public history.
Assurance: internal source/document review, not an independent audit and not a production release approval
Evidence below applies only to the named source, tests, or artifact. The prior local candidate has signature and merged-manifest evidence, but it does not establish those properties for a later public-source commit, and it provides no physical-device or GrapheneOS evidence.
-
The 0.4.2 clipboard-fallback instrumentation waits for the protected Activity's window focus before setting and explicitly reading ciphertext. This matches Android's foreground clipboard boundary and removes a false negative where
RESUMEDpreceded focus; plaintext is still never written to the clipboard. -
The 0.4.1 IME lifecycle fix accepts Android's temporarily missing
InputBinding.connectionTokenonly when the exact liveInputConnectionobject, host UID/package and editor metadata still match. Unit tests reject a different connection even with the same Binder token and metadata, and the one-shotSecureImeBridgeindependently enforces the same referential connection identity before ciphertext publication. -
API 36 manual regression testing confirmed that the embedded Private panel switches RU to EN locally while the Vault is locked. Static policy tests keep the external IME picker blocked and allow only the local language action and explicit shield close while plaintext input is unavailable.
-
vodozemac 0.10.0is exact-versioned with default features disabled and OlmSessionConfig::version_2()enforced. Rust Cargo lockfiles are checked in. -
The latest native test run reported 43 passing tests and the JNI crate reported 3. The suites cover Alice/Bob pairing and matching comparison values, bidirectional traffic, 1000 messages, bounded reordering, replay after state restore, tamper, truncation, Unicode, multipart bounds, state corruption, transactional API boundaries, and canonical-CBOR regressions. Rust format and Clippy checks were also reported passing in the same work session.
-
The complete current Gradle unit gate passes for
:app:testDebugUnitTest,:crypto-core:testDebugUnitTest,:pairing:testDebugUnitTest, and:secure-storage:testDebugUnitTest. Module release lint gates also pass. Two inherited HeliBoard regressions are explicitly@Ignored with issue-specific reasons; the full current gate does not rely on the olderrunTestsbuild- type bypass. -
A pinned
cargo-fuzz 0.13.2/libfuzzer-sys 0.4.13ASan campaign exercised the productionCB1/CBW1presentation and canonical-CBOR envelope parser for 236,453 inputs in 61 seconds with zero crashes, timeouts or artifacts. The tracked harness has five bounded modes and nine seed files. This is useful parser evidence, not a substitute for longer scheduled runs or pairing/JNI fuzz targets. -
:app:connectedDebugAndroidTest --no-configuration-cachepassed 7/7 tests with zero failures/skips on the API 36 x86_64CipherBoard_API_36_AOSPno-Play emulator. The tests verify read-only process-text return behavior, protected-viewer flags/background wiping, ciphertext-only clipboard fallback, two close/reopen storage transactions, and three actual remote-processProcess.killProcess/SIGKILL boundaries. The fault fixture exists only in the debug manifest/source set and is excluded from release. -
The current debug APK also installed and launched its Home activity on that emulator. English and per-app
ru-RUHome hierarchies had bounded, non-overlapping controls; Russian Home also fit in landscape atfont_scale=1.3. AFLAG_SECUREtest capture was fully black, but this is not secure-viewer screenshot acceptance. An inheritedSystemBroadcastReceiverlocale-change self-termination loop found before IME selection was fixed; the rebuilt process remained alive for the recorded three-second observation and exposed the Russian hierarchy. -
Source review confirms
allowBackup=false, all-domain backup/device-transfer exclusions, cleartext denial and noINTERNETorACCESS_NETWORK_STATEdeclaration. The CameraX video/media dependency path that introduced the latter permission is excluded. -
Packageable
:appdependency graphs use strict locking in the checked-inapp/gradle.lockfile. Release SBOM generation consumes the resolved Gradle graph plus locked Rust graphs; the generated artifact still needs manual license review. -
The release preflight verified an official OSV-Scanner v2.4.0 binary against its pinned SHA-256 allowlist, required fresh local Maven and crates.io vulnerability databases, and scanned all 255 CycloneDX SBOM packages fully offline. It exited successfully with zero findings and produced
VULNERABILITY_SCAN.json. The pre-public local signed candidate repeated this gate; the final public tag must repeat it and publish its own report. -
GPLv3/Apache/BlueOak/CC texts, consolidated BSD notices, upstream provenance and the license inventory are generated as offline APK assets, required nonempty by a unit test, and displayed by a non-exported local license activity. Release staging also creates an exact-commit source archive; both outputs still require inspection on the final artifact.
-
Source search found no production HTTP client, Firebase, Google Play Services, analytics, crash-reporting, advertising, WebView, or dynamic-code-loader use. An inherited JVM test uses
HttpURLConnection; it is not packaged production code. -
New Vaults on API 23--29 fail closed to a biometric authentication-per-use Keystore key with validity
-1. The legacy API cannot express the requested biometric-or-device-credential policy without a time-based authorization window, so device-credential fallback is available only on API 30 and newer. Existing pre-release credential-window envelopes remain readable but no new positive-duration legacy key is generated. -
The v0.2 source replaces shield-to-activity navigation with an embedded Private mode panel. Software-key and IME edit paths target a bounded local draft, the active host is scoped by the exact live
InputConnection, host UID/package and editor metadata, and personalized learning/clipboard-history paths are disabled. Detailed inherited InputLogic diagnostics are unconditionally suppressed for the secure editor, including chosen words, n-gram context and code points; a policy regression test covers this gate. Visual emulator inspection confirmed that the panel remains inside the IME bounds and aFLAG_SECUREcapture was black; full host-field/leakage instrumentation on the exact release commit remains required. -
The v0.3 source adds an embedded copied-ciphertext Decrypt mode. One explicit action reads exactly one bounded clipboard text item, the clipboard remains ciphertext, ordinary keys are hidden, and plaintext is owned by a drawing-only surface excluded from selection, Accessibility text, autofill, content capture and saved state. Targeted Robolectric tests for the clipboard reader and surface pass; a real IME/Telegram/GrapheneOS flow remains required.
-
Pending-display acknowledgement now occurs on the first allowed completed plaintext draw, not when bytes are decoded or attached to a view. The pre-draw Vault check and one-shot callback are unit-tested. A three-second render timeout clears without marking the lease, retaining the encrypted recovery record for an exact retry.
-
Worker results posted toward the embedded panel have explicit ownership. Generation cancellation drains queued values and closes late parse/decrypt successes, including their plaintext, instead of silently removing a handler callback. Focused concurrency tests cover post-versus-close races.
-
Vault authentication for the embedded flow uses a process-local random token accepted once by the non-exported activity and completed once back to the controller. The Activity Intent carries no ciphertext or plaintext. Targeted tests cover activation, duplicate completion and cancellation, and the hostless backend fails
VAULT_LOCKEDrather than opening authentication UI. -
The Activity viewer cancels parse/decrypt work on pause, stop, user-leave and UI-hidden transitions, except while its own legacy system-credential result is pending. Its first-draw gate reapplies Vault expiry immediately before rendering. The legacy unlock host is non-exported and no longer uses
noHistory, which would destroy it before an Android 6-10 credential result. -
Pending outbound records now have versioned
READYandCOMMIT_UNCERTAINstates. The latter is durably set before hostcommitText()and is excluded from automatic retry. Codec, store and bridge unit tests exist. Legacy schema-1 pending sends fail safe toCOMMIT_UNCERTAIN, since their prior handoff state is unknowable. The real Binder acknowledgement/process-death window remains an explicit platform test gap. -
The v0.4 source removes the new-send SMS fragmentation selector. Encryption always builds universally fragmented canonical
CB1parts, then persists those parts with a versioned compact/Russian/English presentation enum. Delivery text is deterministically reconstructed and checked against that pending record. Receive auto-detects all presentations; compact remains backward interoperable and legacy SMS-profileCB1parts remain accepted. -
CBW1is a presentation wrapper, not new cryptography. Its tag-first 64-bit truncated SHA-256 checksum is unkeyed and is used only for early corruption rejection. RecoveredCB1parts still undergo canonical validation and Olm/AEAD authentication. Parser bounds are 48 KiB decoded wrapper, 32,768 words, and 384 Ki UTF-16 code units at Android entry points. -
Committed dictionary invariants and byte hashes, compact/Russian/English round trips, mutations, wrong alphabet/version/flags, truncation/extra token, reordered-part, size/count boundaries, property inputs, JNI dispatch, and fuzz corpus cases are covered by source tests. Their exact release-commit run results must be recorded before publishing 0.4; this paragraph does not claim physical GrapheneOS or messenger E2E evidence.
-
Review found and fixed an order-dependence regression in compact multipart decoding. Compact Universal and legacy SMS-profile parts are now bounded, checked for duplicates/mixed metadata, and restored to canonical order before the existing receive path; reversed-profile regressions cover both cases.
-
Review also replaced the app-module raw-string handoff invariant with a typed publication proof.
ValidatedOutboundDeliveryhas a private constructor and requires a native encode/decode exact round trip;PreparedOutboundhas a private constructor and requires an opaque secure-storage commit receipt or recovered pending record whose exact codec bytes match. The bridge therefore cannot be armed with an arbitrary plaintext string through production APIs.
| Area | Current source control | Evidence still required |
|---|---|---|
| Crypto | vodozemac Olm v2; signed offer/response; exact Rust versions | final protocol review, golden vectors, pairing/JNI fuzzing |
| Envelope/presentation | bounded CB1 Base64url/canonical-CBOR map; universal new-send fragmentation; bounded tag-first CBW1 Base4096 Russian/English wrappers; recovered parts receive normal validation; real prior CB1 ASan/libFuzzer campaign and expanded v0.4 harness/corpus |
run and archive the expanded exact-commit campaign; Android/physical messenger tests |
| Replay | 4096 IDs in serialized session plus per-contact 8192-marker SQLite bound committed with inbound state | inbound SIGKILL/reopen test passes; wider long-run/device restart matrix remains |
| Storage | AES-256-GCM records with type/key/schema/revision AAD; random nonces; no-backup CE location | stolen-DB, WAL/SHM, corruption and backup/transfer device tests |
| Keystore | non-exportable AES wrapping key; StrongBox-first; only reported TEE accepted as fallback; software/unknown rejected; user authentication | real StrongBox/TEE/invalidation/reboot tests |
| Send atomicity | advanced ratchet plus contact-bound exact pending ciphertext commit; READY changes durably to COMMIT_UNCERTAIN before one exact-live-connection-scoped host commit; uncertain delivery cannot auto-retry |
existing SIGKILL commit-boundary tests and new codec/store/bridge unit coverage; individual SQLite statements and real host-ack window remain |
| Receive atomicity | replay, advanced ratchet and encrypted pending display commit together; exact-ciphertext digest recovery; pre-first-draw abandon retains record; first allowed draw acknowledges the lease | real post-commit SIGKILL and close/reopen plus targeted first-draw tests pass; in-transaction and post-draw/pre-close kill failpoints remain |
| Private panel | shield toggles an embedded FLAG_SECURE IME panel; bounded Encrypt draft; software keys/edit actions route locally; Decrypt hides keys; no saved state/plaintext copy/share/learning/clipboard history; exact live-connection scope and lifecycle clearing |
locked Encrypt and idle Decrypt states fit API 36 landscape at font scale 2.0 in English/Russian; paired-contact/long-text matrix, hostile-host, hardware-keyboard and physical GrapheneOS evidence remain |
| Decrypt/viewer | explicit bounded ciphertext clipboard read; clipboard unchanged; owned result handoff; one-shot unlock token; drawing-only inaccessible embedded/activity text; render-time Vault gate; background cancellation; local opaque reply capability | targeted v0.3 race/surface tests and 7/7 API 36 process-text/FLAG_SECURE/background-wipe/clipboard instrumentation pass; embedded paired-contact E2E, screenshot/recents/Assistant/Accessibility/screen-lock evidence remains |
| Pairing/contact | signed native offer/response; encrypted one-shot state; bounded orphan cleanup; explicit comparison; changed identity blocks use until verification | live two-device, camera permission, lifecycle, process-kill and hostile-QR instrumentation |
| QR | local ZXing codec and lifecycle-bound CameraX scanner with bounded ASCII payloads; Camera requested only by the Scan actions | real permission grant/deny/revoke and two-device camera evidence |
| Manifest/signing | source backup/cleartext controls, forbidden-permission tests and public release-certificate SHA-256 pin; a pre-public local signed candidate passed scripted APK/signature policy | repeat aapt/apkanalyzer/apksigner verification and publish its evidence for the final public tag |
The previous broad full-app/process-kill evidence blocker is closed for the seven demonstrated API 36 AOSP tests. Specifically:
- process-text returns
RESULT_CANCELEDwith no result data and leaves host text unchanged; the viewer setsFLAG_SECURE, wipes byte/character plaintext on background, and closes its display lease; - ciphertext clipboard fallback occurs only after the explicit action and the original ciphertext clip remains unchanged;
- outbound pending ciphertext and inbound replay/pending-display state remain atomic across ordinary store close/reopen; and
- three debug-only remote
:faultprocess cases use actual SIGKILL before outbound commit, after outbound commit before handoff, and after inbound commit, then assert revision, exact pending ciphertext, replay marker and pending display after reopening storage.
This evidence does not inject a crash between individual SQLite statements
inside one transaction, kill immediately around acknowledgement from a real
host InputConnection.commitText(), or exercise a complete IME/private-panel/live-
camera pairing flow. Android cannot know whether a host accepted commitText()
immediately before process death. The v0.2 handoff marks the operation
COMMIT_UNCERTAIN before that call and does not automatically reinsert it; the
user must inspect the transport. The receiver rejects a duplicate as replay.
These are explicit remaining coverage gaps, not claims of a test pass.
The v0.3 additions have JVM/Robolectric race and surface evidence plus the existing API 36 instrumentation suite. Targeted runs pass for the embedded surface/clipboard, one-shot unlock, owned-result handoff, reply transition, render-time Vault gate, background cancellation and secure-IME lifecycle. The 7/7 device suite confirms process-text plaintext remains inside a protected viewer, its owned buffers wipe on background, and clipboard ciphertext remains unchanged. Manual API 36 inspection confirms locked Encrypt and idle Decrypt controls fit the IME in English and Russian landscape at font scale 2.0, with ordinary keys absent in Decrypt. This does not establish a paired-contact embedded decrypt, BiometricPrompt behavior, a live Telegram integration, or physical GrapheneOS behavior.
The following are assurance prerequisites and residual validation, not known critical source-code defects:
- independent applied-cryptography and Android security review of the exact protocol bytes, state transitions, IME/JNI/storage boundaries and final build;
- physical GrapheneOS operation without Google Play, live two-camera QR pairing, just-in-time permission grant/deny/revoke, and Network permission denial;
- real StrongBox and TEE-only generation, authentication, reboot, timeout and invalidation behavior; and
- screenshot/recents/Accessibility/Assistant/screen-lock validation plus independent hash, signature, permission, SBOM, vulnerability, source/license bundle and installation checks for the exact clean final artifact.
Lack of independent audit or physical-device evidence limits assurance and must be disclosed before high-risk use. It should not be mislabeled as a critical code vulnerability when no such defect has been demonstrated.
- SQLite lookup/replay hashes, record kinds, revisions and timestamps expose bounded count/timing metadata and permit denial-of-service tampering, while secret values and ratchet state remain AEAD-encrypted.
- Embedded clipboard, selected-text and process-text entry paths share bounded plaintext-free parsing but still need hostile transport and device UX coverage across their different Android framework boundaries.
- The display lease's acknowledged flag is process-local until the surface closes and deletes the encrypted pending record. Process death after first draw but before close can therefore leave that record recoverable for the bounded crash-recovery window; v1 is not a strict exactly-once viewer.
- JVM/Android UI objects, public routing/fingerprint summaries and local names cannot be guaranteed to zeroize before garbage collection.
- Android may route a physical keyboard directly to the focused host view, bypassing the IME's local Private draft. Private input is supported only with CipherBoard's on-screen keys; a physical-device warning/host sentinel remains required.
- The 236,453-input ASan/libFuzzer run covers the envelope/presentation parser only; pairing, inner and JNI codecs need additional targets and longer scheduled campaigns. A new exact-commit campaign must still be recorded before release.
- Word presentation is recognizable deterministic ciphertext camouflage, not natural language, steganography, or plausible deniability. It expands text, remains vulnerable to transport truncation/autocorrection/translation, and does not hide sender/recipient, timing, or approximate-size metadata.
- Two CipherBoard 0.4+ peers are required for Russian/English word messages. Compact output is the compatibility choice for an older peer. The recipient's local send setting is irrelevant because receive format detection is automatic.
- Restricted temporary password files avoid command-line disclosure, but PowerShell/JVM memory cannot be guaranteed to zeroize; offline signing remains preferable for a high-assurance release process.
Previously reported source findings for unbounded offer lifetime, secure-mode
paste, release panic handling, and absent invalidated-vault recovery have been
closed in the current tree. Import bounds signed expiry against receiver time;
secure editor/IME actions block paste and clipboard-history paths; Rust release
profiles unwind into the JNI catch_unwind boundary; and destructive vault
reset is offered only after observed key invalidation with explicit confirmation.
The earlier shield-to-activity overlap is also closed by the embedded Private
panel, and the external acknowledgement ambiguity is represented durably as
COMMIT_UNCERTAIN rather than an automatically retryable send.
The embedded secure panel and its dynamically created contact rows now reject
obscured touches recursively, closing the reviewed tapjacking path for
Encrypt, Decrypt, Reply, unlock, contact selection, clear and close actions.
The absence of text actions in transports such as Telegram is addressed by the
v0.3 shield-panel Decrypt mode; plaintext remains inside CipherBoard, while only
copied ciphertext crosses the clipboard boundary.
The v0.4 word-presentation UI and parser still require an exact-release, paired-
contact send/copy/decrypt test in a real messenger on physical GrapheneOS. This
is residual validation, not evidence that the feature has failed.
scripts/verify-apk is designed to fail on forbidden permissions, backup or
cleartext enablement, release debug/test flags, unknown exported shapes,
network deep links, Firebase/GMS/advertising/analytics/crash/WebView/dynamic
loader markers, invalid/non-v2/debug/mismatched-certificate signing and ZIP
alignment failures. Release staging also hashes every published output.
This policy scanner ran successfully against the pre-public local signed candidate. Its local evidence is not tracked or published and must not be used to verify the rewritten public history. It must run again for the final public tag before publication. Even a passing result cannot prove semantic caller validation, absence of native defects, absence of obfuscated behavior, or plaintext non-disclosure. It is a release blocker plus manual review, not an audit.
CipherBoard intentionally provides no in-app updater and requests neither
INTERNET nor REQUEST_INSTALL_PACKAGES. Stable update discovery is delegated
to an external installer such as Obtainium using GitHub Releases; that installer
is a separate networked trust boundary.
- Repeat and archive the seven passing AOSP instrumentation tests on the exact
release commit; add individual SQLite-statement failpoints, the real
InputConnection.commitText()acknowledgement window, and complete IME/private-panel/pairing-camera E2E coverage. - Run the full Gradle/Rust/static/fuzz/crash/leakage suite and repeat the pinned offline OSV scan on a clean commit.
- Exercise physical GrapheneOS, live camera pairing, StrongBox/TEE, authentication/invalidation, direct boot and protected windows as residual platform validation.
- Repeat build/sign/verification on the final public release tag and independently recompute its permissions, hash, certificate and complete artifact manifest; do not reuse the pre-public local candidate evidence.
- Obtain independent Android security and applied-cryptography review of the exact protocol/build and remediate any findings before high-risk reliance.
Сборка реализует проверенные криптографические примитивы и прошла автоматические тесты, но весь продукт не следует считать независимо аудированным до проверки внешним специалистом по прикладной криптографии и Android security.