Skip to content

fix(auth): stop per-socket AsyncLocalStorage heap leak in addTransactionCapability - #2722

Open
carlosfgti wants to merge 2 commits into
WhiskeySockets:developfrom
carlosfgti:fix/auth-transaction-als-leak-develop
Open

fix(auth): stop per-socket AsyncLocalStorage heap leak in addTransactionCapability#2722
carlosfgti wants to merge 2 commits into
WhiskeySockets:developfrom
carlosfgti:fix/auth-transaction-als-leak-develop

Conversation

@carlosfgti

@carlosfgti carlosfgti commented Jul 22, 2026

Copy link
Copy Markdown

Problem

addTransactionCapability created a new AsyncLocalStorage per socket. On Node's legacy async-context propagation (Node 22, pre-AsyncContextFrame), every live AsyncLocalStorage stamps a slot on every pending async resource, so cost grows O(live ALS instances x live async resources). Under reconnection churn each new socket adds another never-disabled instance, and a burst of pending promises/timeouts pushes the heap to OOM even with --max-old-space-size=4096.

Fix

  • Hoist the storage to a single module-level AsyncLocalStorage (one instance per process). The transaction context lives in the run(ctx, work) scope, not on the instance.
  • Key the stored value by a per-store Symbol token (Map<symbol, TransactionContext>) so a wrapped store only ever resolves its own context, even when another wrapped store runs inside its transaction callback. New transactions inherit outer stores' contexts, so cross-store nesting stays isolated.
  • Applies to both the legacy transaction() and the record-scoped transactWith() APIs.

Tests

Adds auth-utils.cross-store-isolation.test.ts covering cross-store context leakage and cross-store nesting. All existing auth-utils* suites pass (8 suites, 25 passed / 1 todo).

Ported from a master-based fix; adapted to the develop transaction refactor (heldLocks, sealed, transactWith, LockManager).


Summary by cubic

Stops a per-socket AsyncLocalStorage heap leak in addTransactionCapability by switching to a single process-wide storage. Preserves cross-store transaction isolation and stability under reconnection churn.

  • Bug Fixes
    • Share one AsyncLocalStorage per process; store contexts in a Map keyed by a per-store Symbol to keep transactions isolated, even when nested across stores.
    • Supports both transaction() and transactWith().
    • Added cross-store isolation regression tests; existing auth-utils tests still pass.

Written for commit 45177dc. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved transaction isolation when multiple stores are used within the same asynchronous workflow.
    • Prevented transaction state and writes from one store from affecting another.
    • Ensured nested transactions across separate stores commit changes to the correct store.
  • Tests

    • Added coverage for cross-store and nested transaction isolation scenarios.

addTransactionCapability created a new AsyncLocalStorage per socket. On
Node's legacy async-context propagation (Node 22, pre-AsyncContextFrame),
every live AsyncLocalStorage stamps a slot on every pending async resource,
so cost grows O(live ALS instances x live async resources). Under
reconnection churn each new socket adds another never-disabled instance,
and a burst of pending promises/timeouts pushes the heap to OOM even with
--max-old-space-size=4096.

Hoist the storage to a module-level singleton. The transaction context
lives in the run(ctx, work) scope rather than on the instance, so distinct
sockets do not collide.
Review follow-up: a process-wide AsyncLocalStorage holding a bare
TransactionContext broke per-store isolation. If a wrapped store B was
used inside store A's transaction callback, B saw A's ambient context and
its writes were committed into A's backing store.

Store the context in a Map keyed by a per-store token. Each store resolves
only its own context, new transactions inherit outer stores' contexts so
nesting across stores stays isolated (both the legacy transaction() and the
record-scoped transactWith() APIs), and the process still keeps a single
AsyncLocalStorage instance (the memory fix). Adds regression tests for both
cross-store leakage and cross-store nesting.
@whiskeysockets-bot

Copy link
Copy Markdown
Contributor

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 works

If you’ve tested this PR, please comment below with:

Tested and working ✅

This helps us speed up the review and merge process.

📦 To test this PR locally:

# NPM
npm install @whiskeysockets/baileys@carlosfgti/Baileys#fix/auth-transaction-als-leak-develop

# Yarn (v2+)
yarn add @whiskeysockets/baileys@carlosfgti/Baileys#fix/auth-transaction-als-leak-develop

# PNPM
pnpm add @whiskeysockets/baileys@carlosfgti/Baileys#fix/auth-transaction-als-leak-develop

If you encounter any issues or have feedback, feel free to comment as well.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 845ff5b3-93e3-4d49-8739-b9feab9e31d3

📥 Commits

Reviewing files that changed from the base of the PR and between e70697f and 45177dc.

📒 Files selected for processing (2)
  • src/Utils/auth-utils.ts
  • src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts

📝 Walkthrough

Walkthrough

The change replaces single-context async-local transaction storage with a shared map keyed by per-store tokens. Transaction entry points inherit and update that map, while new tests verify isolation across independent stores and nested transactions.

Changes

Cross-store transaction isolation

Layer / File(s) Summary
Per-store context map and accessors
src/Utils/auth-utils.ts
Transaction state uses a shared async-local map keyed by store-specific symbols, and transaction-aware reads, writes, and status checks resolve the matching context.
Inherited transaction propagation and isolation
src/Utils/auth-utils.ts, src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts
transaction(...) and transactWith(...) clone the active context map while replacing only the current store’s entry; tests cover independent and nested cross-store transactions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StoreA
  participant transaction
  participant txStorage
  participant StoreB
  StoreA->>transaction: start transaction
  transaction->>txStorage: read shared context map
  transaction->>txStorage: run inherited map with StoreA token
  StoreB->>transaction: start transaction
  transaction->>txStorage: read shared context map
  transaction->>txStorage: run inherited map with StoreB token
Loading

Possibly related PRs

Suggested reviewers: purpshell

Poem

I’m a rabbit with tokens, hopping through maps,
Keeping each store safe from context mishaps.
Nested writes now know where they belong,
A stays with A as B hops along.
Tests thump softly: isolation is strong!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main fix in addTransactionCapability and the AsyncLocalStorage leak cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/Utils/auth-utils.ts (1)

234-247: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace implementation-restating comments with rationale.

These comments describe what storeToken, getContext, and isInTransaction do. Keep only the isolation rationale—preventing an ambient context from another wrapped store being used—or remove them.

As per coding guidelines, “Write comments explaining the why … Do not write comments that restate the code.”

🤖 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/auth-utils.ts` around lines 234 - 247, Replace the comments above
storeToken, getContext, and isInTransaction with a concise rationale explaining
that the store-specific token prevents an ambient transaction context from
another wrapped store being used, or remove the comments if no rationale is
needed; do not describe the symbols’ implementations.

Source: Coding guidelines

src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts (1)

78-95: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add equivalent transactWith() isolation coverage.

This suite only exercises transaction(), while the independently changed transactWith() map-inheritance path needs the same cross-store and nested-store regression protection.

🤖 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/__tests__/Utils/auth-utils.cross-store-isolation.test.ts` around lines 78
- 95, The test suite currently covers nested cross-store isolation only through
transaction(); add equivalent coverage for transactWith(). In the existing
“keeps nested transactions across stores isolated by token” test area, exercise
nested transactWith() calls across a and b, assert both stores report active
transactions, write distinct values, and verify each backing store retains only
its own value.
🤖 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 `@src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts`:
- Around line 35-46: Remove the new any usages in the test store’s get and set
methods: type get<T> using SignalDataTypeMap[T], and iterate set’s input with
Object.keys(d) cast to Array<keyof SignalDataTypeMap> so indexed access remains
type-safe. Preserve the existing fixture behavior while enforcing the
SignalDataTypeMap contract.

In `@src/Utils/auth-utils.ts`:
- Line 446: Update the transaction-entry trace call in the surrounding
transaction flow to use structured logging, passing the lock key in an object
before the message while preserving the existing “entering transaction” text.

---

Nitpick comments:
In `@src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts`:
- Around line 78-95: The test suite currently covers nested cross-store
isolation only through transaction(); add equivalent coverage for
transactWith(). In the existing “keeps nested transactions across stores
isolated by token” test area, exercise nested transactWith() calls across a and
b, assert both stores report active transactions, write distinct values, and
verify each backing store retains only its own value.

In `@src/Utils/auth-utils.ts`:
- Around line 234-247: Replace the comments above storeToken, getContext, and
isInTransaction with a concise rationale explaining that the store-specific
token prevents an ambient transaction context from another wrapped store being
used, or remove the comments if no rationale is needed; do not describe the
symbols’ implementations.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 845ff5b3-93e3-4d49-8739-b9feab9e31d3

📥 Commits

Reviewing files that changed from the base of the PR and between e70697f and 45177dc.

📒 Files selected for processing (2)
  • src/Utils/auth-utils.ts
  • src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/Utils/auth-utils.ts (1)

234-247: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace implementation-restating comments with rationale.

These comments describe what storeToken, getContext, and isInTransaction do. Keep only the isolation rationale—preventing an ambient context from another wrapped store being used—or remove them.

As per coding guidelines, “Write comments explaining the why … Do not write comments that restate the code.”

🤖 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/auth-utils.ts` around lines 234 - 247, Replace the comments above
storeToken, getContext, and isInTransaction with a concise rationale explaining
that the store-specific token prevents an ambient transaction context from
another wrapped store being used, or remove the comments if no rationale is
needed; do not describe the symbols’ implementations.

Source: Coding guidelines

src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts (1)

78-95: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add equivalent transactWith() isolation coverage.

This suite only exercises transaction(), while the independently changed transactWith() map-inheritance path needs the same cross-store and nested-store regression protection.

🤖 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/__tests__/Utils/auth-utils.cross-store-isolation.test.ts` around lines 78
- 95, The test suite currently covers nested cross-store isolation only through
transaction(); add equivalent coverage for transactWith(). In the existing
“keeps nested transactions across stores isolated by token” test area, exercise
nested transactWith() calls across a and b, assert both stores report active
transactions, write distinct values, and verify each backing store retains only
its own value.
🤖 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 `@src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts`:
- Around line 35-46: Remove the new any usages in the test store’s get and set
methods: type get<T> using SignalDataTypeMap[T], and iterate set’s input with
Object.keys(d) cast to Array<keyof SignalDataTypeMap> so indexed access remains
type-safe. Preserve the existing fixture behavior while enforcing the
SignalDataTypeMap contract.

In `@src/Utils/auth-utils.ts`:
- Line 446: Update the transaction-entry trace call in the surrounding
transaction flow to use structured logging, passing the lock key in an object
before the message while preserving the existing “entering transaction” text.

---

Nitpick comments:
In `@src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts`:
- Around line 78-95: The test suite currently covers nested cross-store
isolation only through transaction(); add equivalent coverage for
transactWith(). In the existing “keeps nested transactions across stores
isolated by token” test area, exercise nested transactWith() calls across a and
b, assert both stores report active transactions, write distinct values, and
verify each backing store retains only its own value.

In `@src/Utils/auth-utils.ts`:
- Around line 234-247: Replace the comments above storeToken, getContext, and
isInTransaction with a concise rationale explaining that the store-specific
token prevents an ambient transaction context from another wrapped store being
used, or remove the comments if no rationale is needed; do not describe the
symbols’ implementations.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 845ff5b3-93e3-4d49-8739-b9feab9e31d3

📥 Commits

Reviewing files that changed from the base of the PR and between e70697f and 45177dc.

📒 Files selected for processing (2)
  • src/Utils/auth-utils.ts
  • src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts
🛑 Comments failed to post (2)
src/__tests__/Utils/auth-utils.cross-store-isolation.test.ts (1)

35-46: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove new any usage from the test store.

Record<string, any> and (d as any)[type] bypass the strict fixture contract. Type get<T> against SignalDataTypeMap[T] and iterate Object.keys(d) as Array<keyof SignalDataTypeMap> instead.

As per coding guidelines, “Do not use any in new code. Existing anys in tests are tolerated as warnings, not invitations.”

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 35-37: Recursive/iterative merge copies attacker-controllable keys from a source object into a target via a computed property assignment without rejecting dangerous keys, allowing prototype pollution. Skip or block "proto", "constructor", and "prototype" keys (e.g. if (key === "__proto__" || key === "constructor" || key === "prototype") continue;), use a null-prototype object (Object.create(null)), or use a safe merge utility instead.
Context: for (const id of ids) {
if (id in bucket) out[id] = bucket[id]
}
Note: [CWE-1321] Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

(prototype-pollution-recursive-merge-typescript)

🤖 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/__tests__/Utils/auth-utils.cross-store-isolation.test.ts` around lines 35
- 46, Remove the new any usages in the test store’s get and set methods: type
get<T> using SignalDataTypeMap[T], and iterate set’s input with Object.keys(d)
cast to Array<keyof SignalDataTypeMap> so indexed access remains type-safe.
Preserve the existing fixture behavior while enforcing the SignalDataTypeMap
contract.

Source: Coding guidelines

src/Utils/auth-utils.ts (1)

446-446: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use structured logging for transaction entry.

Include the lock key: logger.trace({ key }, 'entering transaction').

As per coding guidelines, “Use structured logging with objects first and message last.”

🤖 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/auth-utils.ts` at line 446, Update the transaction-entry trace call
in the surrounding transaction flow to use structured logging, passing the lock
key in an object before the message while preserving the existing “entering
transaction” text.

Source: Coding guidelines

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@purpshell

Copy link
Copy Markdown
Member

@codex Write a simple test to reproduce this bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants