🛡️ CVE-2026-32887 — effect

🟠 CVSS 8.0 — High ⚠️ Exploit Public CWE-362 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Effect AsyncLocalStorage context lost/contaminated inside Effect fibers under concurrent load with RPC

Versions

  • effect: 3.19.15
  • @effect/rpc: 0.72.1
  • @effect/platform: 0.94.2
  • Node.js: v22.20.0
  • Vercel runtime with Fluid compute
  • Next.js: 16 (App Router)
  • @clerk/nextjs: 6.x

Root cause

Effect's MixedScheduler batches fiber continuations and drains them inside a single microtask or timer callback. The AsyncLocalStorage context active during that callback belongs to whichever request first triggered the scheduler's drain cycle — not the request that owns the fiber being resumed.

Detailed mechanism

1. Scheduler batching (effect/src/Scheduler.ts, MixedScheduler)

```typescript

// MixedScheduler.starve() — called once when first task is scheduled

private starve(depth = 0) {

if (depth >= this.maxNextTickBeforeTimer) {

setTimeout(() => this.starveInternal(0), 0) // timer queue

} else {

Promise.resolve(void 0).then(() => this.starveInternal(depth + 1)) // microtask queue

}

}

// MixedScheduler.starveInternal() — drains ALL accumulated tasks in one call

private starveInternal(depth: number) {

const tasks = this.tasks.buckets

this.tasks.buckets = []

for (const [_, toRun] of tasks) {

for (let i = 0; i < toRun.length; i++) {

toRun[i]() // ← Every fiber continuation runs in the SAME ALS context

}

}

// ...

}

```

scheduleTask only calls starve() when running is false. Subsequent tasks accumulate in this.tasks until starveInternal drains them all. The Promise.then() (or setTimeout) callback inherits the ALS context from whichever call site created it — i.e., whichever request's fiber first set running = true.

Result: Under concurrent load, fiber continuations from Request A and Request B execute inside the same starveInternal call, sharing a single ALS context. If Request A triggered starve(), then Request B's fiber reads Request A's ALS context.

2. toWebHandlerRuntime does not propagate ALS (@effect/platform/src/HttpApp.ts:211-240)

```typescript

export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {

const httpRuntime: Types.Mutable<Runtime.Runtime<R>> = Runtime.make(runtime)

const run = Runtime.runFork(httpRuntime)

return <E>(self: Default<E, R | Scope.Scope>, middleware?) => {

return (request: Request, context?): Promise<Response> =>

new Promise((resolve) => {

// Per-request Effect context is correctly set via contextMap:

const contextMap = new Map<string, any>(runtime.context.unsafeMap)

const httpServerRequest = ServerRequest.fromWeb(request)

contextMap.set(ServerRequest.HttpServerRequest.key, httpServerRequest)

httpRuntime.context = Context.unsafeMake(contextMap)

// But the fiber is forked without any ALS propagation:

const fiber = run(httpApp as any) // ← ALS context is NOT captured or restored

})

}

}

```

Effect's own Context (containing HttpServerRequest) is correctly set per-request. But the Node.js ALS context — which frameworks like Next.js, Clerk, and OpenTelemetry rely on — is not captured at fork time or restored when the fiber's continuations execute.

3. The dangerous pattern this enables

```typescript

// RPC handler — runs inside an Effect fiber

const handler = Effect.gen(function*() {

// This calls auth() from @clerk/nextjs/server, which reads from ALS

const { userId } = yield* Effect.tryPromise({

try: async () => auth(), // ← may read WRONG user's session

catch: () => new UnauthorizedError({ message: "Auth failed" })

})

return yield* repository.getUser(userId)

})

```

The async () => auth() thunk executes when the fiber continuation is scheduled by MixedScheduler. At that point, the ALS context belongs to an arbitrary concurrent request.

Reproduction scenario

```

Timeline (two concurrent requests to the same toWebHandler endpoint):

T0: Request A arrives → POST handler → webHandler(requestA)

→ Promise executor runs synchronously

→ httpRuntime.context set to A's context

→ fiber A forked, runs first ops synchronously

→ fiber A yields (e.g., at Effect.tryPromise boundary)

→ scheduler.scheduleTask(fiberA_continuation)

→ running=false → starve() called → Promise.resolve().then(drain)

↑ ALS context captured = Request A's context

T1: Request B arrives → POST handler → webHandler(requestB)

→ Promise executor runs synchronously

→ httpRuntime.context set to B's context

→ fiber B forked, runs first ops synchronously

→ fiber B yields

→ scheduler.scheduleTask(fiberB_continuation)

→ running=true → task queued, no new starve()

T2: Microtask fires → starveInternal() runs

→ Drains fiberA_continuation → auth() reads ALS → gets A's context ✓

→ Drains fiberB_continuation → auth() reads ALS → gets A's context ✗ ← WRONG USER

```

Minimal reproduction

```typescript

import { AsyncLocalSto

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is high, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity high, availability none.

Weakness class

CVE-2026-32887 is classified as CWE-362: Race Condition. Concurrent operations share state without proper synchronisation, so timing decides whether the result is correct.

Affected software

CVE-2026-32887 is recorded against 1 package.

  • effect

Timeline and source

Published on 20 March 2026 and last revised on 17 June 2026. A public exploit is known to exist, which raises the urgency of patching considerably. Record sourced from OSV.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
CWE CWE-362
Public Exploit ⚠️ Yes
Source OSV
Published 2026-03-20
Updated 2026-08-12
Modified 2026-06-17
Fix URL N/A

Affected Packages

Software From version Fixed in
effect

Similar Threats

Exploit Protection

Are you running effect?

CVE-2026-32887 carries CVSS 8.0 High rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-32887 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.