Skip to content

Refuse to open a journal on a build without SQLCipher - #139

Open
Jberma23 wants to merge 1 commit into
feature/encrypted-storage-unlock-foundationfrom
feature/require-sqlcipher-at-runtime
Open

Refuse to open a journal on a build without SQLCipher#139
Jberma23 wants to merge 1 commit into
feature/encrypted-storage-unlock-foundationfrom
feature/require-sqlcipher-at-runtime

Conversation

@Jberma23

Copy link
Copy Markdown
Collaborator

What this does

Adds the runtime guard half of #130: the app now refuses to open the journal on a build that has no SQLCipher, instead of silently writing a plaintext one.

PRAGMA key cannot tell you whether SQLCipher is present. SQLite ignores pragmas it does not recognise rather than erroring, so on a stock build the key statement is accepted and does nothing, the schema read after it succeeds against a plaintext file, and the app writes an unencrypted medical journal with no error, no warning, and nothing observable that distinguishes it from the encrypted case. Verified against stock SQLite:

PRAGMA key on plain SQLite: ACCEPTED SILENTLY (no error)
sqlite_master probe still works: {"c":0}
PRAGMA cipher_version -> null

That build is easy to be running. useSQLCipher is applied by a config plugin at prebuild, so Expo Go has never had it, and neither has a stale ios/ or android/ directory generated before the flag was set — and until #129 lands the README still tells contributors to use Expo Go.

PRAGMA cipher_version returns a version string on a SQLCipher build and no row on stock SQLite, which makes it the one cheap way to tell them apart. It asks the library rather than the database, so it is safe ahead of the key — and asking first means a build that cannot encrypt fails against an empty file rather than filling one with plaintext. The error names the remedy (npx expo prebuild + a development build), since whoever hits it is the person who most needs to read it.

0017 refuses an unencrypted fallback on web for exactly this reason: a silent downgrade makes the privacy promise untrue in the way nobody notices until it matters. This applies the same rule to a build that lost SQLCipher, and reports it as SQLCipherUnavailableError rather than a generic open failure — the problem is the binary, not the database, and "could not open the journal database" would send someone looking in the wrong place.

Issue

Addresses #130

Not closing it. #130 has two halves and this is only one: whether a SQLCipher build can read an unkeyed plaintext catalog.db still needs a device, and is untouched here.

Testing

  • Covered by tests

cd mobile && npm test11 suites, 114 tests, all pass (109 before). npx tsc --noEmit and npm run lint clean, on Node 22.20.0.

Five new tests, mutation-checked: commenting out the assertSQLCipher call fails all five and nothing else, so they are genuinely load-bearing rather than passing by coincidence. They cover the refusal itself, that the check happens before PRAGMA key so nothing is written to the file, that the message names the remedy, that the handle is closed, and that it is not reported as a generic DatabaseUnavailableError.

Two existing ordering assertions became index-based casualties of inserting a statement — they now locate PRAGMA key rather than assuming position 0, which is what they actually meant.

Cannot be tested here: that a real SQLCipher build returns a version string. That is the device half of #130.

Notes for review

`PRAGMA key` cannot tell you whether SQLCipher is there. SQLite ignores
pragmas it does not recognise rather than erroring, so on a plain build the
key statement is accepted and does nothing, the schema read after it
succeeds against a plaintext file, and the app writes an unencrypted
medical journal with no error, no warning, and nothing that distinguishes
it from the encrypted case.

That build is easy to be running. `useSQLCipher` is applied by a config
plugin at prebuild, so Expo Go has never had it, and neither has a stale
ios/ or android/ directory generated before the flag was set. Until #129
lands the README still tells contributors to use Expo Go.

`PRAGMA cipher_version` returns a version string on a SQLCipher build and
no row at all on stock SQLite, which makes it the one cheap way to tell
them apart. It asks the library rather than the database, so it is safe
ahead of the key - and asking first means a build that cannot encrypt
fails against an empty file instead of filling one with plaintext.

0017 refuses an unencrypted fallback on web for exactly this reason: a
silent downgrade makes the privacy promise untrue in the way nobody
notices until it matters. This applies the same rule to a build that lost
SQLCipher, and reports it as its own error rather than a generic open
failure, because the problem is the binary and not the database.

Addresses #130. The other half of that issue - whether a SQLCipher build
can read an unkeyed plaintext catalog.db - still needs a device and is
untouched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Base automatically changed from feature/address-encrypted-storage-unlock-foundation to feature/encrypted-storage-unlock-foundation August 18, 2026 01:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant