fix(auth-state): restore AppStateSyncKeyData.fromObject for app-state sync keys - #2685
Open
tgiorgio wants to merge 1 commit into
Open
fix(auth-state): restore AppStateSyncKeyData.fromObject for app-state sync keys#2685tgiorgio wants to merge 1 commit into
tgiorgio wants to merge 1 commit into
Conversation
… sync keys
The three auth-state providers deserialise the persisted app-state sync key
with `proto.Message.AppStateSyncKeyData.create(value)`. `create()` is a bare
constructor that copies own properties with no type coercion, so `keyData`
stays the base64 **string** it was serialised as, instead of becoming bytes.
Serialisation turns it into a string because protobufjs `Message.toJSON()`
uses `util.toJSONOptions` (`bytes: String`), which converts the `Uint8Array`
to base64 before `BufferJSON.replacer` ever sees the value. Baileys' own
reference implementation therefore reads it back with `.fromObject()`, which
base64-decodes `bytes` fields (see `Utils/use-multi-file-auth-state.ts`).
With a string, `hkdf()` evaluates `new Uint8Array("7ae+...")` -> ToIndex(NaN)
-> length 0, so every mutation key is derived from EMPTY key material. The
observable result is app-state sync failing permanently:
Invalid patch mac
failed to sync state from version, removing and trying from scratch
error:1C800064:Provider routines::bad decrypt
resyncing regular from v0 (looping forever)
It is invisible for the first few minutes after pairing, because
`makeCacheableSignalKeyStore` serves the original Buffer-bearing object from
its NodeCache (`SIGNAL_STORE`, 5 min TTL). Once that entry expires the cold
read returns the corrupt string, and the corrupt value is written back into
the cache, so it never recovers.
Everything carried by the `regular` collection is lost, which is how this
surfaces in practice: chat labels applied on the phone never reach the
webhook, `labels.association` stops firing entirely after pairing.
Introduced in 8830f47 (v2.3.3); 2.3.2 is
clean. Present on 2.3.3 through 2.3.7, main and develop.
Verified on 2.3.7 with a real instance: after the change the same instance
resynced `regular` from v0 to v19 with zero decrypt errors, and label events
started flowing again. Keys already persisted in the broken form are
recovered as-is, so no re-pairing is required.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Reviewer's guide (collapsed on small PRs)Reviewer's GuideSwitches app-state sync key deserialization from a raw constructor to protobuf fromObject() in all three auth-state providers so persisted keys are correctly decoded from base64 into byte buffers and state sync resumes working reliably. Sequence diagram for app-state sync key deserialization using fromObjectsequenceDiagram
participant AuthStateProvider
participant Storage as readData
participant Proto as AppStateSyncKeyData
participant Crypto as hkdf
AuthStateProvider->>Storage: readData(app-state-sync-key-id)
Storage-->>AuthStateProvider: value (base64 string)
AuthStateProvider->>Proto: fromObject(value)
Proto-->>AuthStateProvider: keyData (Uint8Array)
AuthStateProvider->>Crypto: hkdf(keyData)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
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.
Problem
The three auth-state providers read the persisted app-state sync key with
proto.Message.AppStateSyncKeyData.create(value):src/utils/use-multi-file-auth-state-prisma.ts:185src/utils/use-multi-file-auth-state-redis-db.ts:64src/utils/use-multi-file-auth-state-provider-files.ts:119create()is a bare constructor: it copies own properties and performs no type coercion.fromObject()is the one that converts, and forbytesfields it base64-decodes a string into aUint8Array. Baileys' own reference implementation usesfromObject()for exactly this reason (Utils/use-multi-file-auth-state.ts).The value is stored as base64 text because protobufjs
Message.toJSON()usesutil.toJSONOptionswithbytes: String, so theUint8Arrayis already a string by the timeBufferJSON.replacerruns. Reading it back withcreate()leaves it a string, and the round trip is asymmetric.Consequence
hkdf()receives a string and evaluatesnew Uint8Array("7ae+...")→ToIndex(NaN)→ length 0. Every mutation key is derived from empty key material:It looks fine for the first few minutes after pairing because
makeCacheableSignalKeyStoreserves the original Buffer-bearing object from its NodeCache (SIGNAL_STORE, 5 min TTL). After expiry the cold read returns the corrupt string — and writes it back into the cache, so it never recovers on its own.The whole
regularcollection is affected. The way it usually surfaces: chat labels applied on the phone stop reaching the webhook,labels.associationfires during pairing and never again.A useful diagnostic: a healthy instance persists
app-state-sync-version-*for all five collections; a broken one only ever hascritical_block.Fix
create(→fromObject(at the three call sites.grep -rn AppStateSyncKeyData src/returns exactly those three hits.Regression origin
Introduced in 8830f47 (bump to v2.3.3). v2.3.2 is clean; v2.3.3 through v2.3.7,
mainanddevelopall carry it. The three files are byte-identical across those refs, so this patch applies unchanged to all of them.Verification
Reproduced and fixed on a real 2.3.7 instance (Baileys 7.0.0-rc.9), with both the Redis and the file storage backends — they fail identically, which is what pointed above the storage layer in the first place.
Empirically, on the same instance:
After the change, the instance resynced
regularfrom v0 to v19 with zero decrypt errors and label events resumed immediately. Keys already persisted in the broken format are recovered as-is — no re-pairing is required, which matters for anyone who has been running a broken instance for a while.Note
This is the same fix as #2593, which was closed for targeting
main. This one targetsdevelopper CONTRIBUTING.🤖 Generated with Claude Code
Summary by Sourcery
Bug Fixes: