Skip to content

[High][store] notifySubscribers iterates live array → subscribers skipped on unsubscribe-during-notify #165

Description

@JosunLP

Severity: 🟠 High (correctness)

Location

src/store/create-store.ts:157-159 (notifySubscribers); unsubscribe splices at :352-360.

Description

$subscribe stores callbacks in a subscribers array and returns an unsubscribe that splices the callback out. notifySubscribers iterates that live array:

for (const callback of subscribers) {
  callback(currentState);
}

If any callback unsubscribes itself (or another subscriber) during notification, the splice shifts indices under the for…of iterator, so the next subscriber is silently skipped for that notification cycle (and a newly-subscribed one may be double-invoked). A watcher that self-detaches on a condition is a common pattern, so this is readily triggered.

Note that $onAction already guards against exactly this with const listenerSnapshot = [...actionListeners];notifySubscribers is simply missing the same treatment.

Reproduction

const store = createStore({ id: 's', state: () => ({ n: 0 }) });
const unsubA = store.$subscribe(() => unsubA());   // self-detaches on first notify
const seen = [];
store.$subscribe(() => seen.push('B'));            // registered after A
store.n = 1;                                        // A runs, splices itself, B is skipped
// seen === []  (B should have fired)

Suggested fix

Snapshot before iterating, matching the $onAction pattern:

for (const callback of [...subscribers]) {
  callback(currentState);
}

Filed as part of a full-codebase security & correctness audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghigh-priorityHigh severitystoreChanges to the store module

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions