Skip to content

THU-420: E2EE Config Updates#602

Open
raivieiraadriano92 wants to merge 13 commits intoraivieiraadriano92/public-config-endpointfrom
raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default
Open

THU-420: E2EE Config Updates#602
raivieiraadriano92 wants to merge 13 commits intoraivieiraadriano92/public-config-endpointfrom
raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default

Conversation

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator

@raivieiraadriano92 raivieiraadriano92 commented Apr 15, 2026

Summary

  • E2EE was always-on, requiring the full encryption setup flow before sync could work. This makes both VITE_E2EE_ENABLED (frontend) and E2EE_ENABLED (backend) default to false, so sync works out of the box without encryption.
  • Operators must set both env vars to "true" to enable E2EE.

Frontend

  • Invert isEncryptionEnabled() to require explicit VITE_E2EE_ENABLED="true"
  • Add needsSyncSetupWizard() shared helper used by both sign-in flow and sync toggle
  • Skip pending device notification modal when E2EE is disabled
  • Update .env.example comments

Backend

  • Add e2eeEnabled setting (E2EE_ENABLED env var, default false)
  • Skip device trust check in validateDeviceForSync when E2EE is disabled
  • Auto-trust devices on upsert when E2EE is disabled (clears stale approvalPending)
  • Scope allowNewDevice bypass to token path only — upload still requires device to exist (defense-in-depth)
  • Update .env.example with new env var

Docs

  • Update docs/e2e-encryption.md Configuration section with both FE and BE env vars

Test plan

  • bun test passes in backend (55 powersync tests, 3 new E2EE settings tests)
  • make check passes (typecheck, lint, format)
  • Fresh deploy with both env vars unset → sync works without encryption wizard
  • Both env vars set to "true" → encryption wizard appears on first sync
  • Revoked devices are still rejected regardless of E2EE flag

🤖 Generated with Claude Code


Note

Medium Risk
Changes sync gating and device trust behavior in powersync token/upload paths; incorrect flag handling could weaken device enforcement or break sync enablement flows.

Overview
E2EE is now disabled by default and controlled by a single backend flag (E2EE_ENABLED), exposed via a new/expanded unauthenticated GET /config response ({ e2eeEnabled }) that the frontend persists in its config store.

PowerSync device enforcement is updated to respect this flag: when E2EE is off, token issuance can create/auto-trust devices (and upgrades existing devices to trusted), while uploads still require an existing non-revoked device; when E2EE is on, the existing trusted-device requirement remains.

On the frontend, isEncryptionEnabled() now reads the persisted backend config (removing VITE_E2EE_ENABLED), adds needsSyncSetupWizard() to gate sync enablement in both sign-in and the sync toggle, and avoids showing pending-device notifications when encryption is disabled; docs/tests are updated accordingly.

Reviewed by Cursor Bugbot for commit 834b290. Bugbot is set up for automated code reviews on this repo. Configure here.

@raivieiraadriano92 raivieiraadriano92 self-assigned this Apr 15, 2026
@raivieiraadriano92 raivieiraadriano92 changed the title feat: make E2EE opt-in for both frontend and backend THU-420: Make E2EE NOT enabled by default Apr 15, 2026
@github-actions
Copy link
Copy Markdown

Semgrep Security Scan

No security issues found.

Comment thread src/db/encryption/config.ts Outdated
@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

Comment thread src/db/encryption/config.ts Outdated
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 15, 2026

PR Metrics

Metric Value
Lines changed (prod code) +139 / -61
JS bundle size (gzipped) 🟢 1.02 MB → 1.02 MB (-175 B, -0.0%)
Test coverage 🟡 70.57% → 70.44% (-0.1%)
Load time (preview) Preview not ready — Render deploy may have timed out

Updated Tue, 21 Apr 2026 15:08:09 GMT · run #1068

@raivieiraadriano92 raivieiraadriano92 marked this pull request as ready for review April 15, 2026 19:50
@greptile-apps

This comment was marked as outdated.

Comment thread src/db/encryption/config.ts Outdated
@claude

This comment was marked as outdated.

Comment thread src/db/encryption/config.ts Outdated
@cjroth cjroth changed the title THU-420: Make E2EE NOT enabled by default THU-420: E2EE Config Updates Apr 15, 2026
@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default branch from 58eb73c to 3caac3f Compare April 15, 2026 23:40
@raivieiraadriano92 raivieiraadriano92 changed the base branch from main to raivieiraadriano92/public-config-endpoint April 15, 2026 23:40
@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default branch from 3caac3f to 2b9ecac Compare April 15, 2026 23:57
@claude

This comment was marked as outdated.

Comment thread src/db/encryption/config.ts Outdated
Comment thread src/hooks/use-app-initialization.ts Outdated
Comment thread src/db/encryption/config.ts Outdated
@github-advanced-security

This comment was marked as off-topic.

Comment thread src/hooks/use-app-initialization.ts Outdated
@claude

This comment was marked as outdated.

@claude

This comment was marked as outdated.

Comment thread src/hooks/use-app-initialization.ts Outdated
Comment thread src/db/encryption/config.ts Outdated
@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default branch from 0f70bab to 7cf3273 Compare April 16, 2026 14:23
@claude

This comment was marked as outdated.

Comment thread src/db/encryption/config.ts Outdated
@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Unhandled promise rejection in handleSignInSuccess: fixed in 5fb96a0
Dynamic import convention violation: fixed in aa14d7b

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in aa14d7b (dynamic import) and 5fb96a0 (async rejection)

3 similar comments
@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in aa14d7b (dynamic import) and 5fb96a0 (async rejection)

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in aa14d7b (dynamic import) and 5fb96a0 (async rejection)

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in aa14d7b (dynamic import) and 5fb96a0 (async rejection)

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in 9cbe8e2fetchConfig now returns null on failure, preserving the cached localStorage value.

1 similar comment
@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

Fixed in 9cbe8e2fetchConfig now returns null on failure, preserving the cached localStorage value.

@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

E2EE downgrade: fixed in 9cbe8e2. Dynamic import: fixed in aa14d7b. Breaking change: by design.

1 similar comment
@raivieiraadriano92
Copy link
Copy Markdown
Collaborator Author

E2EE downgrade: fixed in 9cbe8e2. Dynamic import: fixed in aa14d7b. Breaking change: by design.

@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default branch from 5fb96a0 to 3a64402 Compare April 17, 2026 13:25
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@claude
Copy link
Copy Markdown

claude bot commented Apr 17, 2026

No issues found. The initialization is sequential (config fetch → DB init), the sign-in modal renders only after initData is set, and the device upsert correctly upgrades trust on conflict. LGTM.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Comment thread src/contexts/sign-in-modal-context.tsx Outdated
Comment thread backend/src/dal/devices.ts
Comment thread src/api/config-store.ts
@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/public-config-endpoint branch from e9ccc6f to d8f93ae Compare April 20, 2026 18:23
raivieiraadriano92 and others added 12 commits April 20, 2026 15:23
When fetchConfig fails (network error, timeout), return null instead of
a hardcoded default with e2eeEnabled: false. This prevents silently
overwriting a previously-stored E2EE=true in localStorage during
transient outages, which would disable encryption and cause data
corruption when encrypted data arrives without decryption middleware.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
No circular dependency between config.ts and key-storage.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
handleSignInSuccess was async but passed as () => void callback,
causing unhandled promise rejections if needsSyncSetupWizard or
setSyncEnabled failed. Wrap async work with .catch(console.error)
at the source so errors are logged instead of silently swallowed.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…rage

Remove setEncryptionEnabled, e2eeStorageKey, and the separate e2ee_enabled
localStorage key. isEncryptionEnabled now reads from the Zustand config store
(persisted via thunderbolt-config), which is already hydrated from the /config
endpoint during app initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…isabled

Exercises the allowNewDevice branch where the device doesn't exist in DB,
verifying auto-creation with correct userId, trusted: true, and device name.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@raivieiraadriano92 raivieiraadriano92 force-pushed the raivieiraadriano92/thu-420-make-e2ee-not-enabled-by-default branch from 98ec48d to a0087cf Compare April 20, 2026 19:01
…raadriano92/thu-420-make-e2ee-not-enabled-by-default
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 834b290. Configure here.

this.powerSync = new ThunderboltPowerSyncDatabase(options)
// Use ThunderboltPowerSyncDatabase (with TransformableBucketStorage) only when E2EE is enabled.
// When disabled, the standard PowerSyncDatabase avoids unnecessary middleware overhead.
this.powerSync = isEncryptionEnabled() ? new ThunderboltPowerSyncDatabase(options) : new PowerSyncDatabase(options)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Encryption decision made twice independently in initialize

Low Severity

isEncryptionEnabled() is read independently in getPowerSyncOptions (to configure transformers and sync worker) and again in initialize (to pick the database class). These two decisions are tightly coupled — ThunderboltPowerSyncDatabase is needed precisely when transformers are included — yet they're made via separate, uncorrelated reads. While safe today because both calls are synchronous with no intervening await, this creates a non-obvious coupling where future modifications could cause an inconsistency between the options and the database class.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 834b290. Configure here.

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.

3 participants