feat(datastore): add opt-in SQLCipher encryption support#584
feat(datastore): add opt-in SQLCipher encryption support#584TimeToBuildBob wants to merge 5 commits intoActivityWatch:masterfrom
Conversation
Adds an `encryption` feature flag to aw-datastore (and aw-server) that enables SQLCipher-based database encryption at rest. **Usage**: ``` cargo build --no-default-features --features encryption aw-server --db-password mysecretkey # or: AW_DB_PASSWORD=mysecretkey aw-server ``` **Changes**: - aw-datastore: restructure rusqlite features so `bundled` (default) and `encryption` (opt-in SQLCipher) are mutually exclusive - aw-datastore: add `DatastoreMethod::FileEncrypted(path, key)` variant applying PRAGMA key after connection open - aw-datastore: add `Datastore::new_encrypted()` constructor - aw-server: forward `encryption` / `encryption-vendored` features from aw-datastore; accept --db-password / AW_DB_PASSWORD - tests: add `test_encrypted_datastore_roundtrip` verifying data survives a close/reopen cycle with the correct key Closes ActivityWatch#435
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #584 +/- ##
==========================================
- Coverage 70.81% 67.85% -2.97%
==========================================
Files 51 54 +3
Lines 2916 3201 +285
==========================================
+ Hits 2065 2172 +107
- Misses 851 1029 +178 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR adds opt-in SQLCipher encrypted-database support behind Confidence Score: 5/5Safe to merge; all previous P0/P1 concerns are resolved and remaining findings are minor test-quality suggestions The three previously raised blockers (Debug key leakage, silent wrong-key panic, AW_DB_PASSWORD silently ignored) are all fixed in this HEAD. The only remaining findings are P2: test cleanup not guaranteed on assertion failure, no negative test for wrong-key rejection, and the bundled/encryption feature mutual-exclusivity not being Cargo-enforced. None of these affect production correctness. aw-datastore/tests/datastore.rs — minor test hygiene; aw-datastore/Cargo.toml — feature exclusivity comment Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[aw-server starts] --> B{DB_PASSWORD env var set?}
B -- Yes, encryption compiled --> C[Consume key from CLI/env]
C --> D[Clear env var for child processes]
D --> E[Datastore::new_encrypted]
E --> F[Open SQLCipher connection]
F --> G[Set encryption cipher via PRAGMA]
G --> H[Read user_version to verify cipher]
H -- Failure --> I[panic: bad cipher or not encrypted DB]
H -- OK --> J[Log: Opened encrypted database]
J --> K[DatastoreInstance::new - migrations]
B -- Yes, no encryption compiled --> L[panic: env var set without encryption feature]
B -- No --> M[Datastore::new - plain SQLite]
M --> K
K --> N[Worker thread - request loop]
Reviews (5): Last reviewed commit: "fix(server): panic when AW_DB_PASSWORD s..." | Re-trigger Greptile |
…ssphrase early Two security issues flagged by Greptile review: 1. DatastoreMethod derived Debug, which would expose the raw encryption key in log output, panic messages, or debug instrumentation. Replace derive with a manual Debug impl that redacts the key field as '<redacted>'. 2. PRAGMA key always succeeds even with a wrong passphrase; the actual error only surfaces on the first real SQL query, producing an opaque worker-thread panic. Add an immediate PRAGMA user_version read to validate the key upfront with a clear error message.
|
Addressed the two P1 findings from Greptile in 67e9062:
The P2 (key zeroing via |
1 similar comment
|
Addressed the two P1 findings from Greptile in 67e9062:
The P2 (key zeroing via |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
Per Erik's review on PR ActivityWatch#584: silently warning is wrong because the user explicitly requested encryption — falling back to an unencrypted database violates that intent. Better to refuse to start. The user can either: - rebuild with 'encryption' or 'encryption-vendored' feature, or - unset AW_DB_PASSWORD to use an unencrypted database knowingly.
|
@greptileai review |
|
All review threads are now resolved. Summary of what was addressed since the initial Greptile review:
CI is green on all platforms (ubuntu, macOS, Windows, Android). Greptile 5/5. Ready for merge @ErikBjare — just needs your LGTM. |
…rikBjare/bob#682) (#753) The permission-blocked merge_ready suppression added in #750 only matched four canonical phrases (e.g. "waiting only on a maintainer click", "ready to merge when convenient"). Bob's real-world ready comment on ActivityWatch/aw-server-rust#584 was "Ready for merge @ErikBjare — just needs your LGTM.", which fell through the suppression and re-dispatched as fake-ready work. Add a regex pattern matching "ready (to|for) merge @<maintainer>" — the @-mention is the explicit maintainer-handoff signal that distinguishes a real suppression case from generic "ready to merge once CI passes" prose. Apply the same pattern to both `has_maintainer_waiting_comment` in activity-gate.sh and `is_permission_blocked_merge_ready_pr` in check-notifications.sh so both project-monitoring paths stay aligned. Verified end-to-end against ActivityWatch/aw-server-rust#584: function now returns SUPPRESSED. Closes ErikBjare/bob#682
Summary
Implements encrypted database storage at rest using SQLCipher (via the
rusqlite/bundled-sqlcipherfeature). Data remains fully encrypted on disk; decryption only happens in-process after the key is supplied.Closes #435
Design
SQLCipher is a drop-in replacement for SQLite that transparently encrypts every database page. All existing schema/migration code works unchanged — only the connection-open step gains an extra
PRAGMA keycall.This is implemented as a Cargo feature flag so the default binary stays unchanged. Users who want encryption build with:
cargo build --no-default-features --features encryption # or, for a fully self-contained binary that also vendors OpenSSL: cargo build --no-default-features --features encryption-vendoredFeature flags
bundled(default)encryptionencryption-vendoredbundledandencryption*are mutually exclusive (libsqlite3-sys enforces this). Use--no-default-featureswhen enabling encryption.API changes
aw-datastore:
DatastoreMethod::FileEncrypted(path, key)variant (cfg-gated)Datastore::new_encrypted(dbpath, key, legacy_import)constructoraw-server:
--db-password <KEY>CLI flag (also readable fromAW_DB_PASSWORDenv var)Usage
Changes
aw-datastore/Cargo.toml— restructure rusqlite features; addencryptionandencryption-vendoredaw-datastore/src/lib.rs— addDatastoreMethod::FileEncryptedvariantaw-datastore/src/worker.rs— open encrypted connection withPRAGMA key; addDatastore::new_encrypted()aw-server/Cargo.toml— forward encryption features from aw-datastore; changeaw-datastoredep todefault-features = falseaw-server/src/main.rs—--db-password/AW_DB_PASSWORDoption; selectnew_encrypted()when key is presentaw-datastore/tests/datastore.rs—test_encrypted_datastore_roundtrip: creates encrypted DB, writes events, closes, reopens with same key, verifies data is intactTest plan
cargo check) passes with no errorscargo test --no-default-features --features encryption -- test_encrypted_datastore_roundtrip(requires OpenSSL)aw-server --db-password foo, send heartbeats, stop, verifyfile aw-server-rust.dbshows "SQLite database" is no longer readable as plain SQLite