| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
… 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>
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 commands Interacting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Problem
The three auth-state providers read the persisted app-state sync key with proto.Message.AppStateSyncKeyData.create(value):
create() is a bare constructor: it copies own properties and performs no type coercion. fromObject() is the one that converts, and for bytes fields it base64-decodes a string into a Uint8Array. Baileys' own reference implementation uses fromObject() for exactly this reason (Utils/use-multi-file-auth-state.ts).
The value is stored as base64 text because protobufjs Message.toJSON() uses util.toJSONOptions with bytes: String, so the Uint8Array is already a string by the time BufferJSON.replacer runs. Reading it back with create() leaves it a string, and the round trip is asymmetric.
Consequence
hkdf() receives a string and evaluates new 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 makeCacheableSignalKeyStore serves 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 regular collection is affected. The way it usually surfaces: chat labels applied on the phone stop reaching the webhook, labels.association fires 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 has critical_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, main and develop all 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 regular from 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 targets develop per CONTRIBUTING.
🤖 Generated with Claude Code
Summary by Sourcery
Bug Fixes: