This is a marker, not a plan. Nothing has been audited yet; this issue exists so the pass gets made deliberately rather than never, and so its scope is agreed first.
What we want to do
A full read of the codebase with two questions in mind at once: is anything here a security or privacy risk, and is anything here fragile, dead, or built in a way that will hurt later. One pass, both lenses, because the same code review answers both.
A keyboard is an unusually sensitive thing to install. It sees every password field, every message, every card number typed anywhere on the phone, and this one additionally holds API keys, records audio, and talks to providers over the network. The promises in the README and the privacy policy are only worth what the code actually does, and nobody has ever read the whole thing with an adversary in mind.
Scope
Everything: app/, lib/dictate-core/, the FlorisBoard modules we inherited and have been editing, wear/, and cloud/ (the Cloudflare Worker, including its D1 schema and the wallet flow).
What to look for
Security and privacy
- credentials — how API keys and passwords are stored, logged, backed up, restored and shown; what a rooted device or an
adb backup gets
- the sensitive-field gates — password fields, incognito,
isSensitiveDictationField() — and every path that reads text or audio around them
- what leaves the device, when, and to whom: every network call, every URL built from user text, every prompt that carries a transcript
- logging: anything that could put user text, audio paths or keys into logcat or a crash report
- Android surface: exported components, intent handling, the share-sheet entry points, SAF grants we hold and never release, file permissions on what we write
- the panels that hold user content — clipboard, history, stickers, GIFs — and what deleting something actually deletes
- dependencies with known CVEs, and the ones we vendored (sherpa-onnx, Silero, the dictionaries)
Stability and code health
- crash and ANR paths: work on the main thread, unbounded collections, coroutines and receivers that outlive their scope, anything that can throw inside the IME lifecycle
- the places where we already know a rebuild happens under pressure — text insertion, the window/height chain, the suggestion pipeline
- dead code, settings that no longer do anything, and duplicated logic that has drifted between two copies
How it should end
A written report first, then issues — no fixes inside the audit pass. A finding that gets quietly fixed while reading is a finding nobody can review. Each real finding becomes its own issue with a severity and a concrete failure scenario; the report lists what was checked and found clean as well, because that half is what makes the next audit cheaper.
Anything genuinely exploitable does not go into a public issue — it gets fixed first and described afterwards.
This is a marker, not a plan. Nothing has been audited yet; this issue exists so the pass gets made deliberately rather than never, and so its scope is agreed first.
What we want to do
A full read of the codebase with two questions in mind at once: is anything here a security or privacy risk, and is anything here fragile, dead, or built in a way that will hurt later. One pass, both lenses, because the same code review answers both.
A keyboard is an unusually sensitive thing to install. It sees every password field, every message, every card number typed anywhere on the phone, and this one additionally holds API keys, records audio, and talks to providers over the network. The promises in the README and the privacy policy are only worth what the code actually does, and nobody has ever read the whole thing with an adversary in mind.
Scope
Everything:
app/,lib/dictate-core/, the FlorisBoard modules we inherited and have been editing,wear/, andcloud/(the Cloudflare Worker, including its D1 schema and the wallet flow).What to look for
Security and privacy
adb backupgetsisSensitiveDictationField()— and every path that reads text or audio around themStability and code health
How it should end
A written report first, then issues — no fixes inside the audit pass. A finding that gets quietly fixed while reading is a finding nobody can review. Each real finding becomes its own issue with a severity and a concrete failure scenario; the report lists what was checked and found clean as well, because that half is what makes the next audit cheaper.
Anything genuinely exploitable does not go into a public issue — it gets fixed first and described afterwards.