@aunucore/baileys 1.0.75 โ 1.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/Socket/graphql.js +9 -2
- package/lib/Socket/messages-recv.js +13 -0
- package/lib/Utils/make-mutex.js +9 -9
- package/lib/Utils/messages-media.js +62 -3
- package/lib/Utils/session-watchdog.js +23 -2
- package/lib/Utils/use-multi-file-auth-state.js +8 -1
- package/lib/WABinary/decode.js +29 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,9 +128,9 @@ Everything else is the upstream core. The patch layer stays small and deliberate
|
|
|
128
128
|
|
|
129
129
|
### ๐ฐ Recent releases
|
|
130
130
|
|
|
131
|
+
- **1.0.76 โ Hunt round 3: the decoder + trust-boundary batch.** Every claim in an external deep-audit was verified against the tree (with repros) before anything was patched. The WABinary decoder gets a 512-level nesting cap (a ~100 KB stanza previously recursed 20,000 deep into a raw stack-overflow RangeError) and a 128 MiB zlib output cap (a max-size frame could inflate ~1000ร toward OOM). Media downloads now verify the WhatsApp HMAC on full downloads โ tampered or truncated media errors out instead of decrypting silently โ and download hosts are allowlisted to WhatsApp/Facebook CDN domains, closing a sender-driven SSRF (foreign hosts fall back to `mmg.whatsapp.net`). Auth files are written `0600` (they hold private keys; old `0644` files self-heal on next save), GraphQL error bodies are drained instead of pinning keep-alive sockets, `link_code_companion_reg` key material is length-validated before any pairing secret is derived, the session watchdog also hears raw WebSocket frames (buffered-event delays no longer read as a false deaf session), and a dead `taskTimeout` in the mutex factory is removed. Deliberately untouched, with recorded reasoning: `isTrustedIdentity` (upstream TOFU), unbounded host store arrays, and the app-state transaction design.
|
|
131
132
|
- **1.0.75 โ Partial-config safety + watchdog cleanup.** A partial `transactionOpts` in the socket config can no longer kill every key-store transaction: the config is deep-merged over the defaults and the transaction layer falls back per-field (`maxCommitRetries` 10, `delayBetweenTriesMs` 3000, retries clamped to at least 1 so an explicit `0` means exactly one attempt) โ previously the shallow config spread replaced the whole default object, `maxCommitRetries` came out `undefined`, and every transaction threw `failed to commit transaction mutations` without a single commit attempt. The session watchdog's interval is now stopped when the socket ends (its `stop()` existed but was never called anywhere), closing a one-timer-and-socket-graph leak per reconnect cycle.
|
|
132
133
|
- **1.0.74 โ Fix-all batch.** The entire 1.0.74 heartbeat queue, closed in one version: `resyncAppState` can no longer spin forever holding the key-store transaction mutex (server-omitted collections finish with a warning, plus a 25-round cap), `startByte`-only media downloads no longer silently return an empty stream, an all-failed sticker pack throws Boom 400 instead of uploading empty, key-material guards now check TYPES (Buffer/string only) at six sites so wrong-typed creds fail descriptively instead of as raw TypeErrors, the noise handshake guards a missing `noiseKey` up front, the pre-login `creds.me` dereference family is closed (acks for `<unavailable>` stanzas survive fresh pairing), and two per-entity maps (`RateLimiter.hits`, bug-guard `recentBySender`) evict stale keys with a lazy sweep.
|
|
133
|
-
- **1.0.73 โ Heartbeat queue + a new README.** Three verified heartbeat fixes: a corrupt frame header can no longer wedge `decodeFrame` (zero-length headers are skipped, frames over 10 MiB fail loudly instead of buffering forever), `botRecentSecretsByChat` is capped at 1,000 chats FIFO, and bare key-store writes are serialized through the transaction mutex so a `migrateSession` commit can't overwrite a fresher session record. Plus a fully rewritten README and 46 curated npm keywords.
|
|
134
134
|
Full changelog for every version since 1.0.2: **[docs](https://docsjs.aunucore.eu.cc)** ยท **[GitHub Releases](https://github.com/AunuCore/AunuBaileys-lib/releases)**.
|
|
135
135
|
|
|
136
136
|
### ๐ Credits
|
package/lib/Socket/graphql.js
CHANGED
|
@@ -68,7 +68,12 @@ export const executeWWWGraphQL = async (docId, variables, accessToken, dataPath
|
|
|
68
68
|
body
|
|
69
69
|
});
|
|
70
70
|
if (!res.ok) {
|
|
71
|
-
|
|
71
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ drain the error body before
|
|
72
|
+
// throwing. An unconsumed fetch body pins its keep-alive socket
|
|
73
|
+
// out of the undici pool until GC; repeated GraphQL errors would
|
|
74
|
+
// steadily starve the pool. The body text also improves the error.
|
|
75
|
+
const errBody = await res.text().catch(() => '');
|
|
76
|
+
throw new Boom(`GraphQL HTTP error ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ''}`, { statusCode: res.status });
|
|
72
77
|
}
|
|
73
78
|
const json = await res.json();
|
|
74
79
|
return parseGraphQLResponse(json, dataPath);
|
|
@@ -104,7 +109,9 @@ export const executeWamoGraphQL = async (docId, variables, wamoAuth = {}, dataPa
|
|
|
104
109
|
body: params.toString()
|
|
105
110
|
});
|
|
106
111
|
if (!res.ok) {
|
|
107
|
-
|
|
112
|
+
// AunuBaileys (1.0.76): drain the error body (see executeWWWGraphQL).
|
|
113
|
+
const errBody = await res.text().catch(() => '');
|
|
114
|
+
throw new Boom(`Wamo GraphQL HTTP error ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ''}`, { statusCode: res.status });
|
|
108
115
|
}
|
|
109
116
|
const json = await res.json();
|
|
110
117
|
return parseGraphQLResponse(json, dataPath);
|
|
@@ -523,6 +523,19 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
523
523
|
const ref = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_ref'));
|
|
524
524
|
const primaryIdentityPublicKey = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'primary_identity_pub'));
|
|
525
525
|
const primaryEphemeralPublicKeyWrapped = toRequiredBuffer(getBinaryNodeChildBuffer(linkCodeCompanionReg, 'link_code_pairing_wrapped_primary_ephemeral_pub'));
|
|
526
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ toRequiredBuffer only
|
|
527
|
+
// checks PRESENCE. The wrapped ephemeral key has a fixed
|
|
528
|
+
// wire shape (32B salt + 16B IV + 32B encrypted pubkey =
|
|
529
|
+
// 80) and an identity pubkey is 32B raw / 33B prefixed;
|
|
530
|
+
// anything else is a malformed stanza. Most bad lengths
|
|
531
|
+
// already died inside libsignal BEFORE advSecretKey was
|
|
532
|
+
// assigned, but 31/32-byte inputs slipped through and
|
|
533
|
+
// derived a garbage advSecretKey that then got persisted.
|
|
534
|
+
// Refuse up front instead of deriving creds from junk.
|
|
535
|
+
if (primaryEphemeralPublicKeyWrapped.length < 80 ||
|
|
536
|
+
(primaryIdentityPublicKey.length !== 32 && primaryIdentityPublicKey.length !== 33)) {
|
|
537
|
+
throw new Boom('link_code_companion_reg: malformed key material in server stanza โ refusing to derive pairing secrets from it', { statusCode: 400 });
|
|
538
|
+
}
|
|
526
539
|
const codePairingPublicKey = await decipherLinkPublicKey(primaryEphemeralPublicKeyWrapped);
|
|
527
540
|
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey);
|
|
528
541
|
const random = randomBytes(32);
|
package/lib/Utils/make-mutex.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export const makeMutex = () => {
|
|
2
2
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
3
|
let task = Promise.resolve();
|
|
4
|
-
|
|
4
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ the old `taskTimeout` was dead
|
|
5
|
+
// code (declared, cleared in a finally, NEVER assigned โ there is no
|
|
6
|
+
// hidden timeout safety net, and inventing one that breaks the chain
|
|
7
|
+
// would corrupt the serialization this mutex exists for). Removed.
|
|
8
|
+
// Note: a task that never settles blocks later tasks by design โ that
|
|
9
|
+
// is what a serial mutex means (upstream parity); callers must not
|
|
10
|
+
// await unbounded work inside mutex sections.
|
|
5
11
|
return {
|
|
6
12
|
mutex(code) {
|
|
7
13
|
task = (async () => {
|
|
@@ -11,14 +17,8 @@ export const makeMutex = () => {
|
|
|
11
17
|
await task;
|
|
12
18
|
}
|
|
13
19
|
catch { }
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const result = await code();
|
|
17
|
-
return result;
|
|
18
|
-
}
|
|
19
|
-
finally {
|
|
20
|
-
clearTimeout(taskTimeout);
|
|
21
|
-
}
|
|
20
|
+
// execute the current task
|
|
21
|
+
return await code();
|
|
22
22
|
})();
|
|
23
23
|
// we replace the existing task, appending the new piece of execution to it
|
|
24
24
|
// so the next task will have to wait for this one to finish
|
|
@@ -601,11 +601,24 @@ const toSmallestChunkSize = (num) => {
|
|
|
601
601
|
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
|
|
602
602
|
};
|
|
603
603
|
export const getUrlFromDirectPath = (directPath, fallbackHost) => `https://${fallbackHost || DEF_HOST}${directPath}`;
|
|
604
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ WhatsApp media is only ever served
|
|
605
|
+
// from WhatsApp/Facebook CDN hosts. A sender-supplied URL whose host is
|
|
606
|
+
// anything else is not a media URL, it is an SSRF attempt (the bot would
|
|
607
|
+
// happily GET an internal https host with the sender's directPath glued
|
|
608
|
+
// on). Non-WA hosts are never fetched: with a directPath we fall back to
|
|
609
|
+
// the canonical media host (the path is globally valid there); without
|
|
610
|
+
// one the download is refused.
|
|
611
|
+
const WA_MEDIA_HOST_SUFFIXES = ['.whatsapp.net', '.fbcdn.net'];
|
|
612
|
+
const isWAMediaHost = (hostname) => !!hostname &&
|
|
613
|
+
(hostname === 'whatsapp.net' || hostname === 'fbcdn.net' ||
|
|
614
|
+
WA_MEDIA_HOST_SUFFIXES.some((suffix) => hostname.endsWith(suffix)));
|
|
604
615
|
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
605
616
|
// AunuBaileys (1.0.69): the sender-supplied URL used to be trusted
|
|
606
617
|
// verbatim (host + path) โ a remote peer could trigger a GET to an
|
|
607
618
|
// arbitrary https host. Like upstream v7, only the host is taken
|
|
608
619
|
// from the URL; the path is rebuilt from our own directPath.
|
|
620
|
+
// AunuBaileys (1.0.76): the host is now ALSO allowlisted (above) โ
|
|
621
|
+
// a non-WA host falls back to mmg.whatsapp.net instead of being fetched.
|
|
609
622
|
let downloadUrl;
|
|
610
623
|
if (directPath) {
|
|
611
624
|
let host;
|
|
@@ -613,10 +626,18 @@ export const downloadContentFromMessage = async ({ mediaKey, directPath, url },
|
|
|
613
626
|
host = new URL(url || '').hostname || undefined;
|
|
614
627
|
}
|
|
615
628
|
catch { }
|
|
629
|
+
if (!isWAMediaHost(host)) {
|
|
630
|
+
host = undefined;
|
|
631
|
+
}
|
|
616
632
|
downloadUrl = getUrlFromDirectPath(directPath, host);
|
|
617
633
|
}
|
|
618
634
|
else {
|
|
619
|
-
|
|
635
|
+
let parsed;
|
|
636
|
+
try {
|
|
637
|
+
parsed = new URL(url || '');
|
|
638
|
+
}
|
|
639
|
+
catch { }
|
|
640
|
+
downloadUrl = parsed?.protocol === 'https:' && isWAMediaHost(parsed.hostname) ? url : undefined;
|
|
620
641
|
}
|
|
621
642
|
if (!downloadUrl) {
|
|
622
643
|
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
@@ -628,7 +649,7 @@ export const downloadContentFromMessage = async ({ mediaKey, directPath, url },
|
|
|
628
649
|
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
629
650
|
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
630
651
|
* */
|
|
631
|
-
export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
652
|
+
export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv, macKey }, { startByte, endByte, options } = {}) => {
|
|
632
653
|
let bytesFetched = 0;
|
|
633
654
|
let startChunk = 0;
|
|
634
655
|
let firstBlockIsIV = false;
|
|
@@ -661,6 +682,35 @@ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, {
|
|
|
661
682
|
});
|
|
662
683
|
let remainingBytes = Buffer.from([]);
|
|
663
684
|
let aes;
|
|
685
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ the downloaded ciphertext used
|
|
686
|
+
// to be decrypted with NO integrity check: the 10-byte HMAC-SHA256
|
|
687
|
+
// (macKey, iv || ciphertext) that WhatsApp appends to every media
|
|
688
|
+
// file was silently dropped with the trailing partial block, so a
|
|
689
|
+
// tampering server/CDN could alter media undetected (AES-CBC padding
|
|
690
|
+
// only catches damage to the final block). For FULL downloads (no
|
|
691
|
+
// byte range) with a macKey available, verify it: HMAC every
|
|
692
|
+
// ciphertext byte except a held-back final 10, compare in final().
|
|
693
|
+
// Ranged downloads fetch a slice that excludes the MAC โ they skip
|
|
694
|
+
// verification, exactly like before. Consumers of the stream still
|
|
695
|
+
// receive plaintext progressively, but a failed check errors the
|
|
696
|
+
// stream in final(), so buffered consumers (downloadMediaMessage
|
|
697
|
+
// 'buffer') discard the content wholesale.
|
|
698
|
+
const verifyMac = !!macKey && !startByte && !endByte;
|
|
699
|
+
const macHmac = verifyMac ? Crypto.createHmac('sha256', macKey).update(iv) : null;
|
|
700
|
+
let macTail = Buffer.alloc(0);
|
|
701
|
+
const feedMac = (chunk) => {
|
|
702
|
+
if (!macHmac) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const joined = macTail.length ? Buffer.concat([macTail, chunk]) : chunk;
|
|
706
|
+
if (joined.length > 10) {
|
|
707
|
+
macHmac.update(joined.subarray(0, joined.length - 10));
|
|
708
|
+
macTail = joined.subarray(joined.length - 10);
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
macTail = joined;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
664
714
|
const pushBytes = (bytes, push) => {
|
|
665
715
|
if (startByte || endByte) {
|
|
666
716
|
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
|
|
@@ -680,6 +730,7 @@ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, {
|
|
|
680
730
|
};
|
|
681
731
|
const output = new Transform({
|
|
682
732
|
transform(chunk, _, callback) {
|
|
733
|
+
feedMac(chunk);
|
|
683
734
|
let data = Buffer.concat([remainingBytes, chunk]);
|
|
684
735
|
const decryptLength = toSmallestChunkSize(data.length);
|
|
685
736
|
remainingBytes = data.slice(decryptLength);
|
|
@@ -715,11 +766,19 @@ export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, {
|
|
|
715
766
|
}
|
|
716
767
|
try {
|
|
717
768
|
pushBytes(aes.final(), b => this.push(b));
|
|
718
|
-
callback();
|
|
719
769
|
}
|
|
720
770
|
catch (error) {
|
|
721
771
|
callback(error);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (macHmac) {
|
|
775
|
+
const expectedMac = macHmac.digest().subarray(0, 10);
|
|
776
|
+
if (macTail.length !== 10 || !Crypto.timingSafeEqual(Buffer.from(macTail), expectedMac)) {
|
|
777
|
+
callback(new Boom('Downloaded media failed HMAC integrity verification โ the file was tampered with or truncated in transit; refusing it', { statusCode: 400 }));
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
722
780
|
}
|
|
781
|
+
callback();
|
|
723
782
|
}
|
|
724
783
|
});
|
|
725
784
|
// AunuBaileys (1.0.71): stream.pipe() does NOT forward source
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// - 'connection.update' { sessionWatchdog: { quietForMs, lastInboundAt, at } }
|
|
9
9
|
// once per quiet episode (re-armed by the next inbound message).
|
|
10
10
|
// Inbound traffic is observed on the event buffer ('messages.upsert' /
|
|
11
|
-
// 'messages.update')
|
|
12
|
-
//
|
|
11
|
+
// 'messages.update') AND โ since 1.0.76 โ on the raw WebSocket frame
|
|
12
|
+
// stream when sock.ws is reachable (buffered delivery is deferred during
|
|
13
|
+
// long syncs, which used to read as a false deaf session); hosts can
|
|
14
|
+
// also call watchdog.markInbound() directly. Wiring is opt-in: lib/Socket/index.js
|
|
13
15
|
// attaches it only when config.sessionWatchdog is set (object = options,
|
|
14
16
|
// true = defaults). The interval timer is unref'd so it never keeps a
|
|
15
17
|
// process alive on its own.
|
|
@@ -84,6 +86,12 @@ export const makeSessionWatchdog = (sock, options = {}) => {
|
|
|
84
86
|
ev.off?.('messages.upsert', markInbound);
|
|
85
87
|
ev.off?.('messages.update', markInbound);
|
|
86
88
|
ev.off?.('connection.update', onConnectionUpdate);
|
|
89
|
+
if (rawWs) {
|
|
90
|
+
rawWs.off?.('message', markInbound);
|
|
91
|
+
if (!rawWs.off && typeof rawWs.removeListener === 'function') {
|
|
92
|
+
rawWs.removeListener('message', markInbound);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
87
95
|
return watchdog;
|
|
88
96
|
};
|
|
89
97
|
const getState = () => ({
|
|
@@ -95,6 +103,19 @@ export const makeSessionWatchdog = (sock, options = {}) => {
|
|
|
95
103
|
ev.on('messages.upsert', markInbound);
|
|
96
104
|
ev.on('messages.update', markInbound);
|
|
97
105
|
ev.on('connection.update', onConnectionUpdate);
|
|
106
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ ALSO observe the raw WebSocket
|
|
107
|
+
// frame stream when available. messages.upsert/update travel through
|
|
108
|
+
// the event buffer, which DEFERS delivery to listeners while a
|
|
109
|
+
// buffered function runs (app-state resync, history processing): real
|
|
110
|
+
// inbound messages then fail to re-arm the watchdog for the whole
|
|
111
|
+
// buffering window and a long sync reads as a deaf session โ a false
|
|
112
|
+
// alert. Raw frames are never buffered, and for a deaf-session
|
|
113
|
+
// detector any inbound frame (message, receipt, IQ, keep-alive reply)
|
|
114
|
+
// is the truer liveness signal anyway. Detached again in stop().
|
|
115
|
+
const rawWs = sock.ws && typeof sock.ws.on === 'function' ? sock.ws : null;
|
|
116
|
+
if (rawWs) {
|
|
117
|
+
rawWs.on('message', markInbound);
|
|
118
|
+
}
|
|
98
119
|
const watchdog = {
|
|
99
120
|
markInbound,
|
|
100
121
|
noteInbound: markInbound,
|
|
@@ -45,7 +45,14 @@ export const useMultiFileAuthState = async (folder, logger) => {
|
|
|
45
45
|
try {
|
|
46
46
|
// AunuBaileys: atomic write (tmp + rename) anti-korupsi sesi โ terinspirasi @itsliaaa/baileys
|
|
47
47
|
const tmpPath = `${filePath}.tmp-${process.pid}`;
|
|
48
|
-
|
|
48
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ these files hold
|
|
49
|
+
// private keys (creds.json, session/sender-key records).
|
|
50
|
+
// They used to be created with default permissions (0644
|
|
51
|
+
// on a typical umask) โ readable by every local user on a
|
|
52
|
+
// shared host. Write the tmp file 0600; the rename carries
|
|
53
|
+
// the mode onto the target, so pre-existing 0644 files
|
|
54
|
+
// also self-heal to 0600 on their next save.
|
|
55
|
+
await writeFile(tmpPath, JSON.stringify(data, BufferJSON.replacer), { mode: 0o600 });
|
|
49
56
|
await rename(tmpPath, filePath);
|
|
50
57
|
}
|
|
51
58
|
catch (err) {
|
package/lib/WABinary/decode.js
CHANGED
|
@@ -3,9 +3,24 @@ import { inflate } from 'zlib';
|
|
|
3
3
|
import * as constants from './constants.js';
|
|
4
4
|
import { jidEncode } from './jid-utils.js';
|
|
5
5
|
const inflatePromise = promisify(inflate);
|
|
6
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ inflate used to run with NO output
|
|
7
|
+
// cap: zlib expands up to ~1000x, so a max-size wire frame (10 MiB, HB-91)
|
|
8
|
+
// could inflate to ~10 GB and OOM the process. 128 MiB is far above any
|
|
9
|
+
// legitimate stanza (wire stanzas are messages / IQ results / app-state
|
|
10
|
+
// patches โ big payloads travel as media downloads, not stanzas) while
|
|
11
|
+
// still stopping a bomb cold. Exceeding it is a clean decode failure.
|
|
12
|
+
const MAX_DECOMPRESSED_SIZE = 128 * 1024 * 1024;
|
|
6
13
|
export const decompressingIfRequired = async (buffer) => {
|
|
7
14
|
if (2 & buffer.readUInt8()) {
|
|
8
|
-
|
|
15
|
+
try {
|
|
16
|
+
buffer = await inflatePromise(buffer.slice(1), { maxOutputLength: MAX_DECOMPRESSED_SIZE });
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err?.code === 'ERR_BUFFER_TOO_LARGE') {
|
|
20
|
+
throw new Error(`AunuBaileys: compressed stanza exceeds the ${MAX_DECOMPRESSED_SIZE / (1024 * 1024)} MiB decompressed-size cap โ refusing to inflate (possible zlib bomb)`);
|
|
21
|
+
}
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
9
24
|
}
|
|
10
25
|
else {
|
|
11
26
|
// nodes with no compression have a 0x00 prefix, we remove that
|
|
@@ -13,7 +28,18 @@ export const decompressingIfRequired = async (buffer) => {
|
|
|
13
28
|
}
|
|
14
29
|
return buffer;
|
|
15
30
|
};
|
|
16
|
-
|
|
31
|
+
// AunuBaileys (1.0.76): Hunt-ronde-3 โ the decoder recursed with NO depth
|
|
32
|
+
// bound: ~5 wire bytes buy one nesting level, so a ~100 KB stanza reached
|
|
33
|
+
// depth 20,000 and died as an uncontrolled RangeError (stack overflow).
|
|
34
|
+
// Legit stanzas nest < ~20 deep; 512 is generous headroom, and crossing it
|
|
35
|
+
// is a descriptive decode error instead of a V8 crash. (Total node count
|
|
36
|
+
// needs no separate cap: every node consumes >= 1 input byte, so count is
|
|
37
|
+
// bounded by the โ now also capped โ buffer length.)
|
|
38
|
+
const MAX_NODE_DEPTH = 512;
|
|
39
|
+
export const decodeDecompressedBinaryNode = (buffer, opts, indexRef = { index: 0 }, depth = 0) => {
|
|
40
|
+
if (depth > MAX_NODE_DEPTH) {
|
|
41
|
+
throw new Error(`AunuBaileys: binary node nesting exceeds the ${MAX_NODE_DEPTH}-level safety cap โ refusing to decode (possible depth bomb)`);
|
|
42
|
+
}
|
|
17
43
|
const { DOUBLE_BYTE_TOKENS, SINGLE_BYTE_TOKENS, TAGS } = opts;
|
|
18
44
|
const checkEOS = (length) => {
|
|
19
45
|
if (indexRef.index + length > buffer.length) {
|
|
@@ -186,7 +212,7 @@ export const decodeDecompressedBinaryNode = (buffer, opts, indexRef = { index: 0
|
|
|
186
212
|
const items = [];
|
|
187
213
|
const size = readListSize(tag);
|
|
188
214
|
for (let i = 0; i < size; i++) {
|
|
189
|
-
items.push(decodeDecompressedBinaryNode(buffer, opts, indexRef));
|
|
215
|
+
items.push(decodeDecompressedBinaryNode(buffer, opts, indexRef, depth + 1));
|
|
190
216
|
}
|
|
191
217
|
return items;
|
|
192
218
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aunucore/baileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.76",
|
|
5
5
|
"description": "AunuBaileys โ the self-healing WhatsApp Web API for Node.js. Bad MAC and \"2000 messages into the future\" errors repair themselves, LID and phone-number identities stay in sync, anti-ban is built in, pairing codes are custom โ plus 29 experimental features no other Baileys fork ships: invisible watermarks, per-person tag-all cards, side-profile text, ghost mentions and more. ESM, CJS and full TypeScript types.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"whatsapp",
|