Middleware-based encryption: decrypt on sync, encrypt on upload (alternative to raw tables) #864
Replies: 1 comment
-
|
Thanks for starting this discussion! I think this is an interesting use case. First, to answer your questions:
While your approach isn't broken fundamentally (it will work with the existing protocol), we treat the sync protocol and the functionality of clients as an internal implementation detail we're free to change without taking these transformations into account. So while the approach definitely works, I'd call it hacky in its current state. You're hinting towards something like this eventually being supported officially, and that's probably a direction worth exploring. The behavior of
I'm not aware of any such issues. From the client's perspective, checksums are mostly opaque numbers and we don't try to re-construct them from downloaded data for verification. So this data transformation shouldn't interfere with the sync proces as long as the transferred checksum isn't altered (I believe we mainly need checksums for compact operations and re-deployments, although the latter should likely not need them anymore).
This is an interesting use case, and there are different approaches that come to mind here. For instance, we could potentially have something like the Another option perhaps worth exploring could be to:
This is kind of similar to raw tables, but you wouldn't have to manage the entire schema (triggers are stateless, you could create them after opening the PowerSync database and migrate them by dropping + re-creating them).
I like this idea FWIW. We already support something similar for our Node SDK to let users alter database workers. So I could see a named export of
This is not that easy to implement since we can't send |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Middleware-based encryption: decrypt on sync, encrypt on upload (alternative to raw tables)
We're exploring a middleware-based encryption approach as an alternative to PowerSync's raw tables for E2E encryption. We'd like feedback on whether this fits PowerSync's design and constraints.
Context
PowerSync's E2E encryption with raw tables works, but adds significant complexity (mirror tables, manual migrations, etc.). We're looking for a simpler way to encrypt sensitive columns while keeping normal queries on the local DB.
Approach
We extend PowerSync and override two methods:
generateBucketStorageAdapter()– We return our ownTransformableBucketStorageinstead ofSqliteBucketStorage. This adapter is the one that receives sync data from the Rust client.control()– We overridecontrol()in our storage adapter to interceptPROCESS_TEXT_LINEsync data. Before passing the payload to the WASM engine, we run a middleware pipeline that can transform the data (e.g. decrypt sensitive columns).Flow
adapter.control(PROCESS_TEXT_LINE, payload)→ our override parses the payload, runs middleware (decrypt), then callssuper.control()with the transformed payload → WASM writes to SQLite.uploadData(), we encrypt sensitive columns before sending to the backend.Benefits
enableMultiTabsanduseWebWorkerset tofalseso our adapter is used.Questions
Additional question:
enableMultiTabs/useWebWorkerand custom bucket storageRight now we need
enableMultiTabsanduseWebWorkerset tofalsebecause when they'retrue, PowerSync uses a SharedWorker (SharedSyncImplementation.worker.js) that creates its ownSqliteBucketStoragedirectly (seeSharedSyncImplementation.tsaround line 432:adapter: new SqliteBucketStorage(this.distributedDB!, this.logger)). That path never uses ourgenerateBucketStorageAdapter()override, so our middleware never runs.How we're thinking about overriding it
Custom worker script – PowerSync already supports
sync.worker(path or factory) to override the default SharedWorker. We could ship our own worker that extends or wrapsSharedSyncImplementationand swapsSqliteBucketStoragefor ourTransformableBucketStoragewhen creating the adapter.Main blocker – The worker runs in a separate context and doesn't receive our
PowerSyncDatabaseinstance. It has no access togenerateBucketStorageAdapter()or ourtransformersconfig. We'd need a way to pass a bucket storage factory (or equivalent) into the worker so it can create our adapter instead ofSqliteBucketStorage.Proposed API – Add something like
sync.bucketStorageFactory?: (db: DBAdapter, logger?: ILogger) => BucketStorageAdapter(or a similar hook). When set, the SharedWorker would use this factory instead ofnew SqliteBucketStorage(...). That would let us useenableMultiTabsanduseWebWorkerwhile still running our middleware.Question: Would PowerSync consider a PR that adds a
bucketStorageFactory(or similar) option so custom bucket storage adapters can be used with the SharedWorker path? If so, what shape would you prefer for that API?We're happy to share more implementation details or diagrams if that would help.
Beta Was this translation helpful? Give feedback.
All reactions