CipherBoard welcomes focused bug fixes, tests, documentation, localization, and security improvements. It is a security-sensitive Android input method, so changes that would be routine in another application may alter a plaintext, identity, storage, or ratchet trust boundary here.
By participating, follow the CODE_OF_CONDUCT.md. By
submitting a contribution, you confirm that you have the right to provide it
under the repository's applicable licenses.
- Read
PROJECT_CONTEXT.md,ARCHITECTURE.md, andTHREAT_MODEL.md. - Search open and closed issues for the same problem.
- For a substantial feature, protocol change, new dependency, permission, or trust-boundary change, open a design issue before implementation.
- For an undisclosed vulnerability, stop and follow
SECURITY.mdinstead of opening a public issue or pull request.
Issues, review descriptions, logs, and test fixtures must use synthetic data. Never publish real message plaintext, complete ciphertext, contact names, QR payloads, private keys, session/account state, full fingerprints, Safety Numbers, signing material, or device identifiers.
Contributions must preserve these properties:
- Runtime code has no network capability. Do not add
INTERNET,ACCESS_NETWORK_STATE, localhost communication, network clients, Firebase, Google Play Services, analytics, advertising, or remote crash reporting. - Secure Composer plaintext never reaches the host editor, clipboard, Intent
extras, saved state, logs, notifications, files, learning history, or
analytics. Only ciphertext may pass through
InputConnection.commitText(). - Protected-viewer plaintext is not returned to the source app, copied, shared, indexed, backed up, or retained after its display lifetime.
- Identity, Olm account/session state, contacts, replay state, and pending display records remain encrypted at rest under the authenticated Vault.
- An outbound ratchet advance and its exact pending ciphertext are committed in one transaction before host insertion. An inbound ratchet advance, replay marker, and encrypted pending display are committed in one transaction before rendering.
- Android Auto Backup and device-transfer extraction remain disabled for all application data.
- Pairing remains physical, mutual, signed, expiring, and single-use. Identity changes require explicit re-pairing and are never silently accepted.
- Cryptographic primitives and the Double Ratchet are provided by reviewed, pinned libraries. Do not introduce custom primitives or protocol shortcuts.
- Release policy rejects unreviewed permissions, exported components, ABIs, signing certificates, debug fixtures, test code, and network/telemetry SDKs.
If a proposed feature cannot satisfy an invariant, document the conflict in the design issue rather than weakening the control in code.
Use the pinned versions in BUILD.md. The normal environment uses
JDK 21, Android SDK/Build Tools 36.1.0, NDK 28.0.13004108, Rust/Cargo, and
cargo-ndk. Import the repository root in Android Studio or build from a shell.
Build and verify a debug APK on Linux or macOS:
./scripts/build-debug.shOn Windows PowerShell:
.\scripts\build-debug.ps1The script runs the repository source-policy and Kotlin-style checks, Android
lint, all application/library debug unit tests, the APK build, and manifest/APK
policy verification. It writes a developer APK under dist/.
Never use or commit production signing material. Release signing is deliberately
separate and is described in RELEASE.md.
Run the smallest relevant tests while iterating, then the full debug gate before requesting review.
Android/Kotlin unit tests:
./gradlew :app:testDebugUnitTest \
:crypto-core:testDebugUnitTest \
:pairing:testDebugUnitTest \
:secure-storage:testDebugUnitTestRelease lint:
./gradlew :app:lintRelease \
:crypto-core:lintRelease \
:pairing:lintRelease \
:secure-storage:lintReleaseRust checks:
cargo fmt --all --manifest-path crypto-core/native/Cargo.toml -- --check
cargo clippy --locked --manifest-path crypto-core/native/Cargo.toml \
--all-targets --all-features -- -D warnings
cargo test --locked --manifest-path crypto-core/native/Cargo.toml
cargo fmt --all --manifest-path crypto-core/jni/Cargo.toml -- --check
cargo clippy --locked --manifest-path crypto-core/jni/Cargo.toml \
--all-targets --all-features -- -D warnings
cargo test --locked --manifest-path crypto-core/jni/Cargo.tomlAndroid instrumentation requires a running API 36 emulator or device:
./gradlew :crypto-core:connectedDebugAndroidTest \
:app:connectedDebugAndroidTestChanges to ratchet transactions, process recovery, IME handoff, pairing, QR,
selected-text parsing, storage, Keystore handling, or protected UI require the
corresponding instrumentation and fault-injection coverage in
TEST_PLAN.md. Parser changes require regression/property tests
and an appropriate fuzz run. Do not use real secrets as fixtures or snapshots.
- Keep Rust dependency versions exact and lockfiles reviewed.
- Use
vodozemacAPIs rather than reimplementing cryptography. - Keep the JNI surface minimal, stateless, bounded, panic-contained, and free of long-lived native handles.
- Zeroize owned secret buffers where practical and keep errors content-free.
- Update
CRYPTO_PROTOCOL.md, the crypto ADR, compatibility vectors, negative tests, property tests, and fuzz corpora when wire/state behavior changes. - Treat a protocol version, canonical encoding, transcript, routing tag, replay, skipped-key, or message-limit change as a security design change.
- Preserve optimistic revisions and single SQLite transactions around ratchet transitions.
- Test process death before and after each durable boundary. A retry may reuse the exact stored ciphertext, but must never encrypt again from stale state.
- Keep all secret records in credential-encrypted, no-backup storage.
- Do not rely on SharedPreferences or the application sandbox alone for secret state.
- Handle StrongBox absence, TEE fallback, Vault lock, screen lock, reboot, and Keystore invalidation explicitly.
- Test both ordinary HeliBoard input and Secure Composer behavior.
- Verify Russian, English, emoji, symbols, multiline fields, orientation, and password-field warnings when input behavior changes.
- Do not store secret text in
String, ViewModel,SavedStateHandle, singleton, static field, long-lived coroutine, filename, or test output when a bounded, wipeable representation is practical. - Protected UI must retain
FLAG_SECURE, background clearing, disabled selection/share/autofill/content capture, and no lifecycle-state restoration. - Add every user-visible string to both
values/andvalues-ru/. Check long Russian text, large fonts, TalkBack labels for non-secret controls, landscape, and RTL layout behavior.
- Discuss every new runtime dependency before adding it. Record its exact version, license, provenance, runtime behavior, and vulnerability review.
- Update dependency locks intentionally; never introduce a floating version.
- A new permission or exported component needs a written threat analysis and release-policy update. Network, contacts, SMS, overlay, package-query, and accessibility-service permissions are outside version 1 scope.
- QR processing must remain fully local and must not depend on Play Services or a cloud recognition API.
- Keep
THIRD_PARTY_NOTICES.md,LICENSES.md, SBOM generation, and GPL corresponding-source obligations current.
CipherBoard retains a large AOSP/OpenBoard/HeliBoard codebase. Useful entry points include:
- key/touch input:
PointerTracker,LatinIME, andInputLogic; - host editor communication:
RichInputConnection; - suggestions:
DictionaryFacilitatorImpl,Suggest, and the suggestion strip; - layouts: asset layout files,
KeyboardParser, andTextKeyData; - settings:
SettingsValues,Settings, andDefault.
Keep unrelated upstream refactors out of security changes. Preserve existing
copyright headers, mark modifications where required, and document any upstream
cherry-pick in UPSTREAM.md or the pull request.
A reviewable pull request:
- addresses one coherent problem and links its issue or design discussion;
- explains user-visible behavior and security/privacy impact;
- identifies changed trust boundaries and failure modes;
- contains focused tests, including negative and crash-boundary cases where relevant;
- updates English and Russian resources and affected architecture/security docs;
- reports the exact commands run and their results;
- contains no generated APK, keystore, password, local SDK path, secret fixture, or unrelated formatting churn; and
- keeps the worktree compatible with the full debug build script.
Use the repository pull request template. Maintainers may request a smaller change, additional tests, or external specialist review before accepting a security-sensitive modification.