Skip to content

fix(zone.js): add iteration cap to drainMicroTaskQueueSynchronously to prevent DoS#68906

Draft
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/zone.js_dos_microtask
Draft

fix(zone.js): add iteration cap to drainMicroTaskQueueSynchronously to prevent DoS#68906
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/zone.js_dos_microtask

Conversation

@arturovt
Copy link
Copy Markdown
Contributor

Previously, the microtask drain loop had no upper bound on how many times it could refill and re-drain the queue. Since microtasks are allowed to enqueue additional microtasks while executing, a pathological task chain could keep the queue perpetually non-empty, causing the loop to spin indefinitely and block the main thread.

This can happen in several ways:

  1. Mutual Promise recursion — the simplest and most common case:
const loop = () => Promise.resolve().then(loop);
loop();

Each .then() callback schedules another microtask through Zone’s patched Promise implementation, so _microTaskQueue is refilled on every iteration and the drain loop never yields back to the event loop.

  1. A compromised or buggy third-party dependency that continuously re-schedules itself using Zone.current.scheduleMicroTask() from inside its own callback, either intentionally or due to broken retry/polling logic.

  2. An accidental zone-patched cycle in Angular application code, such as a Promise chain inside an HTTP interceptor, router guard, or APP_INITIALIZER that unintentionally re-triggers itself under certain runtime conditions.

This change introduces MAX_MICROTASK_DRAIN_ITERATIONS = 1000. The counter increments once per pass through the drain loop, and if the limit is exceeded, the remaining queue is cleared to immediately unblock the main thread. onUnhandledError is then invoked with a descriptive error message that includes the remaining queued task count so the failure is surfaced visibly instead of hanging silently.

The limit is intentionally set high enough to avoid impacting legitimate workloads (Angular’s own change detection and routing complete in well under 10 iterations), while still providing protection against runaway or malicious microtask cycles.

…o prevent DoS

Previously, the microtask drain loop had no upper bound on how many times it could refill and re-drain the queue. Since microtasks are allowed to enqueue additional microtasks while executing, a pathological task chain could keep the queue perpetually non-empty, causing the loop to spin indefinitely and block the main thread.

This can happen in several ways:

1. Mutual Promise recursion — the simplest and most common case:

```js
const loop = () => Promise.resolve().then(loop);
loop();
```

Each `.then()` callback schedules another microtask through Zone’s patched Promise implementation, so `_microTaskQueue` is refilled on every iteration and the drain loop never yields back to the event loop.

2. A compromised or buggy third-party dependency that continuously re-schedules itself using `Zone.current.scheduleMicroTask()` from inside its own callback, either intentionally or due to broken retry/polling logic.

3. An accidental zone-patched cycle in Angular application code, such as a Promise chain inside an HTTP interceptor, router guard, or `APP_INITIALIZER` that unintentionally re-triggers itself under certain runtime conditions.

This change introduces `MAX_MICROTASK_DRAIN_ITERATIONS = 1000`. The counter increments once per pass through the drain loop, and if the limit is exceeded, the remaining queue is cleared to immediately unblock the main thread. `onUnhandledError` is then invoked with a descriptive error message that includes the remaining queued task count so the failure is surfaced visibly instead of hanging silently.

The limit is intentionally set high enough to avoid impacting legitimate workloads (Angular’s own change detection and routing complete in well under 10 iterations), while still providing protection against runaway or malicious microtask cycles.
@angular-robot angular-robot Bot added the area: zones Issues related to zone.js label May 24, 2026
@ngbot ngbot Bot added this to the Backlog milestone May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: zones Issues related to zone.js

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant