fix(sync): don't reject recoverable data as tampering (#9256) - #9259
Merged
Conversation
A SuperSync (E2EE) user could not recover their data after a fresh
install: sync aborted with an OperationIntegrityError ("possible
sync-server tampering", GHSA-8pxh-mgc7-gp3g). It was a false positive.
Root cause, two layers:
- #7628 (v18.10.0) added `allowFetchFallback` and `altPublicLinkHost`
to JiraCfg as REQUIRED fields with no `?` and no backfill (rule 11).
A Jira provider configured earlier persists without them.
- The v18.15.0 full-state integrity check ran strict typia validation
on the decrypted SYNC_IMPORT in the download path and threw on ANY
error — before the apply path's own Checkpoint D repair could heal
the drift. So the security gate was stricter than the app's own
loader and turned benign version-drift into a hard rejection.
Fix:
- Type the two fields optional (rule 11); all read sites already treat
a missing value as the default and DEFAULT_JIRA_CFG still sets both.
- Make the integrity check heal-tolerant: when strict validation fails
but every error is field-level drift within a present root, mirror
the apply path's autoFixTypiaErrors on a throwaway clone and accept
if it then validates. A missing top-level root (the opType-promotion
attack shape) still rejects without healing.
- Guard the one hole a naive heal leaves: typia descends into a
present-but-mis-typed root (e.g. `globalConfig: []`) and reports only
nested errors, which autoFixTypiaErrors' globalConfig catch-all would
rebuild from defaults. `_hasWrongRootContainerKind` rejects any root
whose container kind disagrees with the model, so a mis-typed section
can never heal through. Fail closed if the heal probe throws.
The security boundary is those two structural filters, not what
autoFixTypiaErrors happens to be able to rebuild.
Contributor
Preview Deployment
Branch: Deployed with Cloudflare Pages |
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9256.
The problem
A SuperSync (E2EE) user could not recover their data after a fresh install. After entering the correct encryption password, sync aborted with an
OperationIntegrityError— "possible sync-server tampering" (GHSA-8pxh-mgc7-gp3g). It's a false positive: the server data is fine.Console signature from the report:
Root cause (two layers)
allowFetchFallbackandaltPublicLinkHosttoJiraCfgas required fields with no?and no backfill. A Jira provider configured before that persists without them.assertDecryptedFullStateOpIntegrityruns strict typia validation on the decryptedSYNC_IMPORTin the download path and throws on any error — before the apply path's own Checkpoint‑D repair (dataRepair → autoFixTypiaErrors) could heal the drift. So the security gate was stricter than the app's own loader, turning benign version‑drift into a hard rejection and blocking recovery.The fix
DEFAULT_JIRA_CFGstill sets both.autoFixTypiaErrorson a throwawaystructuredCloneand accept if it then validates. A missing top‑level root — the opType‑promotion attack shape, where a single‑entity op is relabelledSYNC_IMPORT— still rejects without healing.globalConfig: []) and reports only nested errors, whichautoFixTypiaErrors'globalConfigcatch‑all would rebuild from defaults._hasWrongRootContainerKindrejects any root whose container kind (array vs object) disagrees with the model, so a mis‑typed section can never heal through. Fails closed if the heal probe throws.The security boundary is those two structural filters — not what
autoFixTypiaErrorshappens to be able to rebuild. A promoted single‑entity payload is missing whole top‑level sections and auto‑fix never fabricates them, so the GHSA‑8pxh‑mgc7‑gp3g promotion defense is preserved.Testing
verify-decrypted-op-integrity.spec.tsgains 7 unit tests against the realfrozen-state-v18.15snapshot: accepts a valid snapshot; accepts one missing the #7628 fields (regression for #9256); heals a still‑required drifted field; rejects a promoted single‑entity payload, a snapshot missing a whole section, and a mis‑typed root (globalConfig: []); ignores non‑full‑state ops. The heal‑path and container‑kind tests were sabotage‑verified (each fails on the wrong implementation).This change was hardened via a multi‑agent review — the
globalConfig: []container‑kind gap was caught by an adversarial pass and closed here.