CWE-362
Allowed-with-ReviewConcurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
Abstraction: Class · Status: Draft
The product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently.
2900 vulnerabilities reference this CWE, most recent first.
GHSA-36CG-4JFF-5863
Vulnerability from github – Published: 2021-08-25 20:59 – Updated: 2023-06-13 20:41Affected versions of this crate unconditionally implement Send/Sync for SyncChannel. SyncChannel doesn't provide access to &T but merely serves as a channel that consumes and returns owned T.
Users can create UB in safe Rust by sending T: !Send to other threads with SyncChannel::send/recv APIs. Using T = Arc<Cell<_> allows to create data races (which can lead to memory corruption), and using T = MutexGuard allows to unlock a mutex from a thread that didn't lock the mutex.
{
"affected": [
{
"package": {
"ecosystem": "crates.io",
"name": "signal-simple"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.1.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2020-36446"
],
"database_specific": {
"cwe_ids": [
"CWE-119",
"CWE-362"
],
"github_reviewed": true,
"github_reviewed_at": "2021-08-09T21:44:31Z",
"nvd_published_at": "2021-08-08T06:15:00Z",
"severity": "HIGH"
},
"details": "Affected versions of this crate unconditionally implement Send/Sync for SyncChannel\u003cT\u003e. SyncChannel\u003cT\u003e doesn\u0027t provide access to \u0026T but merely serves as a channel that consumes and returns owned T.\n\nUsers can create UB in safe Rust by sending T: !Send to other threads with SyncChannel::send/recv APIs. Using T = Arc\u003cCell\u003c_\u003e allows to create data races (which can lead to memory corruption), and using T = MutexGuard\u003cT\u003e allows to unlock a mutex from a thread that didn\u0027t lock the mutex.",
"id": "GHSA-36cg-4jff-5863",
"modified": "2023-06-13T20:41:14Z",
"published": "2021-08-25T20:59:13Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-36446"
},
{
"type": "WEB",
"url": "https://github.com/kitsuneninetails/signal-rust/issues/2"
},
{
"type": "WEB",
"url": "https://raw.githubusercontent.com/rustsec/advisory-db/main/crates/signal-simple/RUSTSEC-2020-0126.md"
},
{
"type": "WEB",
"url": "https://rustsec.org/advisories/RUSTSEC-2020-0126.html"
},
{
"type": "PACKAGE",
"url": "http://github.com/kitsuneninetails/signal-rust"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Data races in signal-simple"
}
GHSA-36X9-553R-V339
Vulnerability from github – Published: 2025-08-19 18:31 – Updated: 2025-11-26 21:31In the Linux kernel, the following vulnerability has been resolved:
nfsd: avoid ref leak in nfsd_open_local_fh()
If two calls to nfsd_open_local_fh() race and both successfully call nfsd_file_acquire_local(), they will both get an extra reference to the net to accompany the file reference stored in *pnf.
One of them will fail to store (using xchg()) the file reference in *pnf and will drop that reference but WON'T drop the accompanying reference to the net. This leak means that when the nfs server is shut down it will hang in nfsd_shutdown_net() waiting for &nn->nfsd_net_free_done.
This patch adds the missing nfsd_net_put().
{
"affected": [],
"aliases": [
"CVE-2025-38567"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-08-19T17:15:33Z",
"severity": "MODERATE"
},
"details": "In the Linux kernel, the following vulnerability has been resolved:\n\nnfsd: avoid ref leak in nfsd_open_local_fh()\n\nIf two calls to nfsd_open_local_fh() race and both successfully call\nnfsd_file_acquire_local(), they will both get an extra reference to the\nnet to accompany the file reference stored in *pnf.\n\nOne of them will fail to store (using xchg()) the file reference in\n*pnf and will drop that reference but WON\u0027T drop the accompanying\nreference to the net. This leak means that when the nfs server is shut\ndown it will hang in nfsd_shutdown_net() waiting for\n\u0026nn-\u003enfsd_net_free_done.\n\nThis patch adds the missing nfsd_net_put().",
"id": "GHSA-36x9-553r-v339",
"modified": "2025-11-26T21:31:25Z",
"published": "2025-08-19T18:31:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-38567"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/c4bf8f26c51e51bbb840935659a7b3b65a802c07"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/e5a73150776f18547ee685c9f6bfafe549714899"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/fcb0a417fddb605530c4837e0996620f8ed38023"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-3734-5VQH-GP62
Vulnerability from github – Published: 2022-05-14 02:18 – Updated: 2022-05-14 02:18Race condition in the s_xout kernel module in Sun Solstice X.25 9.2, when running on a multiple CPU machine, allows local users to cause a denial of service (panic) via vectors involving reading the /dev/xty file.
{
"affected": [],
"aliases": [
"CVE-2008-5009"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2008-11-10T15:23:00Z",
"severity": "MODERATE"
},
"details": "Race condition in the s_xout kernel module in Sun Solstice X.25 9.2, when running on a multiple CPU machine, allows local users to cause a denial of service (panic) via vectors involving reading the /dev/xty file.",
"id": "GHSA-3734-5vqh-gp62",
"modified": "2022-05-14T02:18:22Z",
"published": "2022-05-14T02:18:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2008-5009"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/46466"
},
{
"type": "WEB",
"url": "http://secunia.com/advisories/32667"
},
{
"type": "WEB",
"url": "http://securitytracker.com/id?1021156"
},
{
"type": "WEB",
"url": "http://sunsolve.sun.com/search/document.do?assetkey=1-21-108669-21-1"
},
{
"type": "WEB",
"url": "http://sunsolve.sun.com/search/document.do?assetkey=1-21-108670-21-1"
},
{
"type": "WEB",
"url": "http://sunsolve.sun.com/search/document.do?assetkey=1-26-243106-1"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/32215"
},
{
"type": "WEB",
"url": "http://www.vupen.com/english/advisories/2008/3087"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-37C3-XQFQ-PGFJ
Vulnerability from github – Published: 2022-05-24 19:06 – Updated: 2022-05-24 19:06When Web Render components were destructed, a race condition could have caused undefined behavior, and we presume that with enough effort may have been exploitable to run arbitrary code. This vulnerability affects Firefox < 88.0.1 and Firefox for Android < 88.1.3.
{
"affected": [],
"aliases": [
"CVE-2021-29952"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-06-24T14:15:00Z",
"severity": "HIGH"
},
"details": "When Web Render components were destructed, a race condition could have caused undefined behavior, and we presume that with enough effort may have been exploitable to run arbitrary code. This vulnerability affects Firefox \u003c 88.0.1 and Firefox for Android \u003c 88.1.3.",
"id": "GHSA-37c3-xqfq-pgfj",
"modified": "2022-05-24T19:06:09Z",
"published": "2022-05-24T19:06:09Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-29952"
},
{
"type": "WEB",
"url": "https://bugzilla.mozilla.org/show_bug.cgi?id=1704227"
},
{
"type": "WEB",
"url": "https://www.mozilla.org/security/advisories/mfsa2021-20"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-37QM-M2W9-H5HJ
Vulnerability from github – Published: 2026-03-02 21:31 – Updated: 2026-03-06 06:30In multiple locations, there is a possible lockscreen bypass due to a race condition. This could lead to local escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.
{
"affected": [],
"aliases": [
"CVE-2025-48568"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-03-02T19:16:25Z",
"severity": "HIGH"
},
"details": "In multiple locations, there is a possible lockscreen bypass due to a race condition. This could lead to local escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.",
"id": "GHSA-37qm-m2w9-h5hj",
"modified": "2026-03-06T06:30:29Z",
"published": "2026-03-02T21:31:29Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-48568"
},
{
"type": "WEB",
"url": "https://source.android.com/docs/security/bulletin/2026/2026-03-01"
},
{
"type": "WEB",
"url": "https://source.android.com/security/bulletin/2026-03-01"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-37XV-HHG6-7Q67
Vulnerability from github – Published: 2022-07-07 00:00 – Updated: 2022-07-15 00:00In TEEI driver, there is a possible use after free due to a race condition. This could lead to local escalation of privilege with System execution privileges needed. User interaction is not needed for exploitation. Patch ID: ALPS06641388; Issue ID: ALPS06641388.
{
"affected": [],
"aliases": [
"CVE-2022-21773"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-07-06T14:15:00Z",
"severity": "MODERATE"
},
"details": "In TEEI driver, there is a possible use after free due to a race condition. This could lead to local escalation of privilege with System execution privileges needed. User interaction is not needed for exploitation. Patch ID: ALPS06641388; Issue ID: ALPS06641388.",
"id": "GHSA-37xv-hhg6-7q67",
"modified": "2022-07-15T00:00:24Z",
"published": "2022-07-07T00:00:25Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-21773"
},
{
"type": "WEB",
"url": "https://corp.mediatek.com/product-security-bulletin/July-2022"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-38CC-GG86-24CH
Vulnerability from github – Published: 2026-04-14 18:30 – Updated: 2026-04-14 18:30Concurrent execution using shared resource with improper synchronization ('race condition') in Windows SSDP Service allows an authorized attacker to elevate privileges locally.
{
"affected": [],
"aliases": [
"CVE-2026-32068"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-14T18:17:06Z",
"severity": "HIGH"
},
"details": "Concurrent execution using shared resource with improper synchronization (\u0027race condition\u0027) in Windows SSDP Service allows an authorized attacker to elevate privileges locally.",
"id": "GHSA-38cc-gg86-24ch",
"modified": "2026-04-14T18:30:39Z",
"published": "2026-04-14T18:30:39Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32068"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-32068"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-38F7-945M-QR2G
Vulnerability from github – Published: 2026-03-20 20:34 – Updated: 2026-03-25 18:11Versions
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)
// 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)
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
// 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
import { AsyncLocalStorage } from "node:async_hooks"
import { Effect, Layer } from "effect"
import { RpcServer, RpcSerialization, Rpc, RpcGroup } from "@effect/rpc"
import { HttpServer } from "@effect/platform"
import * as S from "effect/Schema"
// Simulate a framework's ALS (like Next.js / Clerk)
const requestStore = new AsyncLocalStorage<{ userId: string }>()
class GetUser extends Rpc.make("GetUser", {
success: S.Struct({ userId: S.String, alsUserId: S.String }),
failure: S.Never,
payload: {}
}) {}
const MyRpc = RpcGroup.make("MyRpc").add(GetUser)
const MyRpcLive = MyRpc.toLayer(
RpcGroup.toHandlers(MyRpc, {
GetUser: () =>
Effect.gen(function*() {
// Simulate calling an ALS-dependent API inside an Effect fiber
const alsResult = yield* Effect.tryPromise({
try: async () => {
const store = requestStore.getStore()
return store?.userId ?? "NONE"
},
catch: () => { throw new Error("impossible") }
})
return { userId: "from-effect-context", alsUserId: alsResult }
})
})
)
const RpcLayer = MyRpcLive.pipe(
Layer.provideMerge(RpcSerialization.layerJson),
Layer.provideMerge(HttpServer.layerContext)
)
const { handler } = RpcServer.toWebHandler(MyRpc, { layer: RpcLayer })
// Simulate two concurrent requests with different ALS contexts
async function main() {
const results = await Promise.all([
requestStore.run({ userId: "user-A" }, () => handler(makeRpcRequest("GetUser"))),
requestStore.run({ userId: "user-B" }, () => handler(makeRpcRequest("GetUser"))),
])
// Parse responses and check if alsUserId matches the expected user
// Under the bug: both responses may show "user-A" (or one shows the other's)
for (const res of results) {
console.log(await res.json())
}
}
Impact
| Symptom | Severity |
|---|---|
auth() returns wrong user's session |
Critical — authentication bypass |
cookies() / headers() from Next.js read wrong request |
High — data leakage |
| OpenTelemetry trace context crosses requests | Medium — incorrect traces |
| Works locally, fails in production | Hard to diagnose — only manifests under concurrent load |
Workaround
Capture ALS-dependent values before entering the Effect runtime and pass them via Effect's own context system:
// In the route handler — OUTSIDE the Effect fiber (ALS is correct here)
export const POST = async (request: Request) => {
const { userId } = await auth() // ← Safe: still in Next.js ALS context
// Inject into request headers or use the `context` parameter
const headers = new Headers(request.headers)
headers.set("x-clerk-auth-user-id", userId ?? "")
const enrichedRequest = new Request(request.url, {
method: request.method,
headers,
body: request.body,
duplex: "half" as any,
})
return webHandler(enrichedRequest)
}
// In Effect handlers — read from HttpServerRequest headers instead of calling auth()
const getAuthenticatedUserId = Effect.gen(function*() {
const req = yield* HttpServerRequest.HttpServerRequest
const userId = req.headers["x-clerk-auth-user-id"]
if (!userId) return yield* Effect.fail(new UnauthorizedError({ message: "Auth required" }))
return userId
})
Suggested fix (for Effect maintainers)
Option A: Propagate ALS context through the scheduler
Capture the AsyncLocalStorage snapshot when a fiber continuation is scheduled, and restore it when the continuation executes:
// In MixedScheduler or the fiber runtime
import { AsyncLocalStorage } from "node:async_hooks"
scheduleTask(task: Task, priority: number) {
// Capture current ALS context
const snapshot = AsyncLocalStorage.snapshot()
this.tasks.scheduleTask(() => snapshot(task), priority)
// ...
}
AsyncLocalStorage.snapshot() (Node.js 20.5+) returns a function that, when called, restores the ALS context from the point of capture. This ensures each fiber continuation runs with its originating request's ALS context.
Trade-off: Adds one closure allocation per scheduled task. Could be opt-in via a FiberRef or scheduler option.
Option B: Capture ALS at runFork and restore per fiber step
When Runtime.runFork is called, capture the ALS snapshot and associate it with the fiber. Before each fiber step (in the fiber runtime's evaluateEffect loop), restore the snapshot.
Trade-off: More invasive but provides correct ALS propagation for the fiber's entire lifetime, including across flatMap chains and Effect.tryPromise thunks.
Option C: Document the limitation and provide a context injection API
If ALS propagation is intentionally not supported, document this prominently and provide a first-class API for toWebHandler to accept per-request context. The existing context?: Context.Context<never> parameter on the handler function partially addresses this, but it requires callers to know about the issue and manually extract values before entering Effect.
Related
- Node.js
AsyncLocalStoragedocs: https://nodejs.org/api/async_context.html AsyncLocalStorage.snapshot(): https://nodejs.org/api/async_context.html#static-method-asynclocalstoragesnapshot- Next.js uses ALS for
cookies(),headers(),auth()in App Router - Similar issue pattern in other fiber-based runtimes (e.g., ZIO has
FiberRefpropagation for this)
POC replica of my setup
// Create web handler from Effect RPC
// sharedMemoMap ensures all RPC routes share the same connection pool
const { handler: webHandler, dispose } = RpcServer.toWebHandler(DemoRpc, {
layer: RpcLayer,
memoMap: sharedMemoMap,
});
/**
* POST /api/rpc/demo
*/
export const POST = async (request: Request) => {
return webHandler(request);
};
registerDispose(dispose);
Used util functions
/**
* Creates a dispose registry that collects dispose callbacks and runs them
* when `runAll` is invoked. Handles both sync and async dispose functions,
* catching errors to prevent one failing dispose from breaking others.
*
* @internal Exported for testing — use `registerDispose` in application code.
*/
export const makeDisposeRegistry = () => {
const disposeFns: Array<() => void | Promise<void>> = []
const runAll = () => {
for (const fn of disposeFns) {
try {
const result = fn()
if (result && typeof result.then === "function") {
result.then(undefined, (err: unknown) => console.error("Dispose error:", err))
}
} catch (err) {
console.error("Dispose error:", err)
}
}
}
const register = (dispose: () => void | Promise<void>) => {
disposeFns.push(dispose)
}
return { register, runAll }
}
export const registerDispose: (dispose: () => void | Promise<void>) => void = globalValue(
Symbol.for("@global/RegisterDispose"),
() => {
const registry = makeDisposeRegistry()
if (typeof process !== "undefined") {
process.once("beforeExit", registry.runAll)
}
return registry.register
}
)
The actual effect that was run within the RPC context that the bug was found
``` export const getAuthenticatedUserId: Effect.Effect = Effect.gen(function() { const authResult = yield Effect.tryPromise({ try: async () => auth(), catch: () => new UnauthorizedError({ message: "Failed to get auth session" }) })
if (!authResult.userId) {
return yield* Effect.fail(
new UnauthorizedError({
message: "Authentication required"
})
)
}
return authResult.userId
}) ```
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "effect"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.20.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32887"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-20T20:34:06Z",
"nvd_published_at": "2026-03-20T22:16:27Z",
"severity": "HIGH"
},
"details": "## Versions\n\n- `effect`: 3.19.15\n- `@effect/rpc`: 0.72.1\n- `@effect/platform`: 0.94.2\n- Node.js: v22.20.0\n- Vercel runtime with Fluid compute\n- Next.js: 16 (App Router)\n- `@clerk/nextjs`: 6.x\n\n## Root cause\n\nEffect\u0027s `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\u0027s drain cycle \u2014 **not** the request that owns the fiber being resumed.\n\n### Detailed mechanism\n\n#### 1. Scheduler batching (`effect/src/Scheduler.ts`, `MixedScheduler`)\n\n```typescript\n// MixedScheduler.starve() \u2014 called once when first task is scheduled\nprivate starve(depth = 0) {\n if (depth \u003e= this.maxNextTickBeforeTimer) {\n setTimeout(() =\u003e this.starveInternal(0), 0) // timer queue\n } else {\n Promise.resolve(void 0).then(() =\u003e this.starveInternal(depth + 1)) // microtask queue\n }\n}\n\n// MixedScheduler.starveInternal() \u2014 drains ALL accumulated tasks in one call\nprivate starveInternal(depth: number) {\n const tasks = this.tasks.buckets\n this.tasks.buckets = []\n for (const [_, toRun] of tasks) {\n for (let i = 0; i \u003c toRun.length; i++) {\n toRun[i]() // \u2190 Every fiber continuation runs in the SAME ALS context\n }\n }\n // ...\n}\n```\n\n`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 \u2014 i.e., whichever request\u0027s fiber first set `running = true`.\n\n**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\u0027s fiber reads Request A\u0027s ALS context.\n\n#### 2. `toWebHandlerRuntime` does not propagate ALS (`@effect/platform/src/HttpApp.ts:211-240`)\n\n```typescript\nexport const toWebHandlerRuntime = \u003cR\u003e(runtime: Runtime.Runtime\u003cR\u003e) =\u003e {\n const httpRuntime: Types.Mutable\u003cRuntime.Runtime\u003cR\u003e\u003e = Runtime.make(runtime)\n const run = Runtime.runFork(httpRuntime)\n return \u003cE\u003e(self: Default\u003cE, R | Scope.Scope\u003e, middleware?) =\u003e {\n return (request: Request, context?): Promise\u003cResponse\u003e =\u003e\n new Promise((resolve) =\u003e {\n // Per-request Effect context is correctly set via contextMap:\n const contextMap = new Map\u003cstring, any\u003e(runtime.context.unsafeMap)\n const httpServerRequest = ServerRequest.fromWeb(request)\n contextMap.set(ServerRequest.HttpServerRequest.key, httpServerRequest)\n httpRuntime.context = Context.unsafeMake(contextMap)\n\n // But the fiber is forked without any ALS propagation:\n const fiber = run(httpApp as any) // \u2190 ALS context is NOT captured or restored\n })\n }\n}\n```\n\nEffect\u0027s own `Context` (containing `HttpServerRequest`) is correctly set per-request. But the **Node.js ALS context** \u2014 which frameworks like Next.js, Clerk, and OpenTelemetry rely on \u2014 is not captured at fork time or restored when the fiber\u0027s continuations execute.\n\n#### 3. The dangerous pattern this enables\n\n```typescript\n// RPC handler \u2014 runs inside an Effect fiber\nconst handler = Effect.gen(function*() {\n // This calls auth() from @clerk/nextjs/server, which reads from ALS\n const { userId } = yield* Effect.tryPromise({\n try: async () =\u003e auth(), // \u2190 may read WRONG user\u0027s session\n catch: () =\u003e new UnauthorizedError({ message: \"Auth failed\" })\n })\n return yield* repository.getUser(userId)\n})\n```\n\nThe `async () =\u003e auth()` thunk executes when the fiber continuation is scheduled by `MixedScheduler`. At that point, the ALS context belongs to an arbitrary concurrent request.\n\n## Reproduction scenario\n\n```\nTimeline (two concurrent requests to the same toWebHandler endpoint):\n\nT0: Request A arrives \u2192 POST handler \u2192 webHandler(requestA)\n \u2192 Promise executor runs synchronously\n \u2192 httpRuntime.context set to A\u0027s context\n \u2192 fiber A forked, runs first ops synchronously\n \u2192 fiber A yields (e.g., at Effect.tryPromise boundary)\n \u2192 scheduler.scheduleTask(fiberA_continuation)\n \u2192 running=false \u2192 starve() called \u2192 Promise.resolve().then(drain)\n \u2191 ALS context captured = Request A\u0027s context\n\nT1: Request B arrives \u2192 POST handler \u2192 webHandler(requestB)\n \u2192 Promise executor runs synchronously\n \u2192 httpRuntime.context set to B\u0027s context\n \u2192 fiber B forked, runs first ops synchronously\n \u2192 fiber B yields\n \u2192 scheduler.scheduleTask(fiberB_continuation)\n \u2192 running=true \u2192 task queued, no new starve()\n\nT2: Microtask fires \u2192 starveInternal() runs\n \u2192 Drains fiberA_continuation \u2192 auth() reads ALS \u2192 gets A\u0027s context \u2713\n \u2192 Drains fiberB_continuation \u2192 auth() reads ALS \u2192 gets A\u0027s context \u2717 \u2190 WRONG USER\n```\n\n## Minimal reproduction\n\n```typescript\nimport { AsyncLocalStorage } from \"node:async_hooks\"\nimport { Effect, Layer } from \"effect\"\nimport { RpcServer, RpcSerialization, Rpc, RpcGroup } from \"@effect/rpc\"\nimport { HttpServer } from \"@effect/platform\"\nimport * as S from \"effect/Schema\"\n\n// Simulate a framework\u0027s ALS (like Next.js / Clerk)\nconst requestStore = new AsyncLocalStorage\u003c{ userId: string }\u003e()\n\nclass GetUser extends Rpc.make(\"GetUser\", {\n success: S.Struct({ userId: S.String, alsUserId: S.String }),\n failure: S.Never,\n payload: {}\n}) {}\n\nconst MyRpc = RpcGroup.make(\"MyRpc\").add(GetUser)\n\nconst MyRpcLive = MyRpc.toLayer(\n RpcGroup.toHandlers(MyRpc, {\n GetUser: () =\u003e\n Effect.gen(function*() {\n // Simulate calling an ALS-dependent API inside an Effect fiber\n const alsResult = yield* Effect.tryPromise({\n try: async () =\u003e {\n const store = requestStore.getStore()\n return store?.userId ?? \"NONE\"\n },\n catch: () =\u003e { throw new Error(\"impossible\") }\n })\n return { userId: \"from-effect-context\", alsUserId: alsResult }\n })\n })\n)\n\nconst RpcLayer = MyRpcLive.pipe(\n Layer.provideMerge(RpcSerialization.layerJson),\n Layer.provideMerge(HttpServer.layerContext)\n)\n\nconst { handler } = RpcServer.toWebHandler(MyRpc, { layer: RpcLayer })\n\n// Simulate two concurrent requests with different ALS contexts\nasync function main() {\n const results = await Promise.all([\n requestStore.run({ userId: \"user-A\" }, () =\u003e handler(makeRpcRequest(\"GetUser\"))),\n requestStore.run({ userId: \"user-B\" }, () =\u003e handler(makeRpcRequest(\"GetUser\"))),\n ])\n\n // Parse responses and check if alsUserId matches the expected user\n // Under the bug: both responses may show \"user-A\" (or one shows the other\u0027s)\n for (const res of results) {\n console.log(await res.json())\n }\n}\n```\n\n## Impact\n\n| Symptom | Severity |\n|---------|----------|\n| `auth()` returns wrong user\u0027s session | **Critical** \u2014 authentication bypass |\n| `cookies()` / `headers()` from Next.js read wrong request | **High** \u2014 data leakage |\n| OpenTelemetry trace context crosses requests | **Medium** \u2014 incorrect traces |\n| Works locally, fails in production | Hard to diagnose \u2014 only manifests under concurrent load |\n\n## Workaround\n\nCapture ALS-dependent values **before** entering the Effect runtime and pass them via Effect\u0027s own context system:\n\n```typescript\n// In the route handler \u2014 OUTSIDE the Effect fiber (ALS is correct here)\nexport const POST = async (request: Request) =\u003e {\n const { userId } = await auth() // \u2190 Safe: still in Next.js ALS context\n\n // Inject into request headers or use the `context` parameter\n const headers = new Headers(request.headers)\n headers.set(\"x-clerk-auth-user-id\", userId ?? \"\")\n const enrichedRequest = new Request(request.url, {\n method: request.method,\n headers,\n body: request.body,\n duplex: \"half\" as any,\n })\n\n return webHandler(enrichedRequest)\n}\n\n// In Effect handlers \u2014 read from HttpServerRequest headers instead of calling auth()\nconst getAuthenticatedUserId = Effect.gen(function*() {\n const req = yield* HttpServerRequest.HttpServerRequest\n const userId = req.headers[\"x-clerk-auth-user-id\"]\n if (!userId) return yield* Effect.fail(new UnauthorizedError({ message: \"Auth required\" }))\n return userId\n})\n```\n\n## Suggested fix (for Effect maintainers)\n\n### Option A: Propagate ALS context through the scheduler\n\nCapture the `AsyncLocalStorage` snapshot when a fiber continuation is scheduled, and restore it when the continuation executes:\n\n```typescript\n// In MixedScheduler or the fiber runtime\nimport { AsyncLocalStorage } from \"node:async_hooks\"\n\nscheduleTask(task: Task, priority: number) {\n // Capture current ALS context\n const snapshot = AsyncLocalStorage.snapshot()\n this.tasks.scheduleTask(() =\u003e snapshot(task), priority)\n // ...\n}\n```\n\n`AsyncLocalStorage.snapshot()` (Node.js 20.5+) returns a function that, when called, restores the ALS context from the point of capture. This ensures each fiber continuation runs with its originating request\u0027s ALS context.\n\n**Trade-off:** Adds one closure allocation per scheduled task. Could be opt-in via a `FiberRef` or scheduler option.\n\n### Option B: Capture ALS at `runFork` and restore per fiber step\n\nWhen `Runtime.runFork` is called, capture the ALS snapshot and associate it with the fiber. Before each fiber step (in the fiber runtime\u0027s `evaluateEffect` loop), restore the snapshot.\n\n**Trade-off:** More invasive but provides correct ALS propagation for the fiber\u0027s entire lifetime, including across `flatMap` chains and `Effect.tryPromise` thunks.\n\n### Option C: Document the limitation and provide a `context` injection API\n\nIf ALS propagation is intentionally not supported, document this prominently and provide a first-class API for `toWebHandler` to accept per-request context. The existing `context?: Context.Context\u003cnever\u003e` parameter on the handler function partially addresses this, but it requires callers to know about the issue and manually extract values before entering Effect.\n\n## Related\n\n- Node.js `AsyncLocalStorage` docs: https://nodejs.org/api/async_context.html\n- `AsyncLocalStorage.snapshot()`: https://nodejs.org/api/async_context.html#static-method-asynclocalstoragesnapshot\n- Next.js uses ALS for `cookies()`, `headers()`, `auth()` in App Router\n- Similar issue pattern in other fiber-based runtimes (e.g., ZIO has `FiberRef` propagation for this)\n\n\n## POC replica of my setup\n\n```\n// Create web handler from Effect RPC\n// sharedMemoMap ensures all RPC routes share the same connection pool\nconst { handler: webHandler, dispose } = RpcServer.toWebHandler(DemoRpc, {\n layer: RpcLayer,\n memoMap: sharedMemoMap,\n});\n\n/**\n * POST /api/rpc/demo\n */\nexport const POST = async (request: Request) =\u003e {\n return webHandler(request);\n};\n\nregisterDispose(dispose);\n```\n\n### Used util functions\n\n```\n\n/**\n * Creates a dispose registry that collects dispose callbacks and runs them\n * when `runAll` is invoked. Handles both sync and async dispose functions,\n * catching errors to prevent one failing dispose from breaking others.\n *\n * @internal Exported for testing \u2014 use `registerDispose` in application code.\n */\nexport const makeDisposeRegistry = () =\u003e {\n const disposeFns: Array\u003c() =\u003e void | Promise\u003cvoid\u003e\u003e = []\n\n const runAll = () =\u003e {\n for (const fn of disposeFns) {\n try {\n const result = fn()\n if (result \u0026\u0026 typeof result.then === \"function\") {\n result.then(undefined, (err: unknown) =\u003e console.error(\"Dispose error:\", err))\n }\n } catch (err) {\n console.error(\"Dispose error:\", err)\n }\n }\n }\n\n const register = (dispose: () =\u003e void | Promise\u003cvoid\u003e) =\u003e {\n disposeFns.push(dispose)\n }\n\n return { register, runAll }\n}\n\nexport const registerDispose: (dispose: () =\u003e void | Promise\u003cvoid\u003e) =\u003e void = globalValue(\n Symbol.for(\"@global/RegisterDispose\"),\n () =\u003e {\n const registry = makeDisposeRegistry()\n\n if (typeof process !== \"undefined\") {\n process.once(\"beforeExit\", registry.runAll)\n }\n\n return registry.register\n }\n)\n```\n\n### The actual effect that was run within the RPC context that the bug was found\n\n```\nexport const getAuthenticatedUserId: Effect.Effect\u003cstring, UnauthorizedError\u003e =\n Effect.gen(function*() {\n const authResult = yield* Effect.tryPromise({\n try: async () =\u003e auth(),\n catch: () =\u003e\n new UnauthorizedError({\n message: \"Failed to get auth session\"\n })\n })\n\n if (!authResult.userId) {\n return yield* Effect.fail(\n new UnauthorizedError({\n message: \"Authentication required\"\n })\n )\n }\n\n return authResult.userId\n })\n ```",
"id": "GHSA-38f7-945m-qr2g",
"modified": "2026-03-25T18:11:18Z",
"published": "2026-03-20T20:34:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Effect-TS/effect/security/advisories/GHSA-38f7-945m-qr2g"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32887"
},
{
"type": "PACKAGE",
"url": "https://github.com/Effect-TS/effect"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Effect `AsyncLocalStorage` context lost/contaminated inside Effect fibers under concurrent load with RPC"
}
GHSA-38GP-WR3C-CQW7
Vulnerability from github – Published: 2022-05-24 16:52 – Updated: 2024-04-04 01:29cPanel before 68.0.27 allows attackers to read root's crontab file during a short time interval upon the enabling of backups (SEC-342).
{
"affected": [],
"aliases": [
"CVE-2018-20940"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-08-01T17:15:00Z",
"severity": "LOW"
},
"details": "cPanel before 68.0.27 allows attackers to read root\u0027s crontab file during a short time interval upon the enabling of backups (SEC-342).",
"id": "GHSA-38gp-wr3c-cqw7",
"modified": "2024-04-04T01:29:05Z",
"published": "2022-05-24T16:52:10Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-20940"
},
{
"type": "WEB",
"url": "https://documentation.cpanel.net/display/CL/68+Change+Log"
},
{
"type": "WEB",
"url": "https://news.cpanel.com/cpanel-tsr-2018-0001-full-disclosure"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-38PP-34CQ-8XPR
Vulnerability from github – Published: 2022-05-24 17:19 – Updated: 2022-06-03 00:00A race condition was addressed with improved state handling. This issue is fixed in iOS 13.5 and iPadOS 13.5, macOS Catalina 10.15.5, tvOS 13.4.5, watchOS 6.2.5. An application may be able to gain elevated privileges.
{
"affected": [],
"aliases": [
"CVE-2020-9839"
],
"database_specific": {
"cwe_ids": [
"CWE-362"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-06-09T17:15:00Z",
"severity": "MODERATE"
},
"details": "A race condition was addressed with improved state handling. This issue is fixed in iOS 13.5 and iPadOS 13.5, macOS Catalina 10.15.5, tvOS 13.4.5, watchOS 6.2.5. An application may be able to gain elevated privileges.",
"id": "GHSA-38pp-34cq-8xpr",
"modified": "2022-06-03T00:00:35Z",
"published": "2022-05-24T17:19:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-9839"
},
{
"type": "WEB",
"url": "https://support.apple.com/HT211168"
},
{
"type": "WEB",
"url": "https://support.apple.com/HT211170"
},
{
"type": "WEB",
"url": "https://support.apple.com/HT211171"
},
{
"type": "WEB",
"url": "https://support.apple.com/HT211175"
},
{
"type": "WEB",
"url": "http://packetstormsecurity.com/files/159084/macOS-cfprefsd-Arbitrary-File-Write-Local-Privilege-Escalation.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
Mitigation
In languages that support it, use synchronization primitives. Only wrap these around critical code to minimize the impact on performance.
Mitigation
Use thread-safe capabilities such as the data access abstraction in Spring.
Mitigation
- Minimize the usage of shared resources in order to remove as much complexity as possible from the control flow and to reduce the likelihood of unexpected conditions occurring.
- Additionally, this will minimize the amount of synchronization necessary and may even help to reduce the likelihood of a denial of service where an attacker may be able to repeatedly trigger a critical section (CWE-400).
Mitigation
When using multithreading and operating on shared variables, only use thread-safe functions.
Mitigation
Use atomic operations on shared variables. Be wary of innocent-looking constructs such as "x++". This may appear atomic at the code layer, but it is actually non-atomic at the instruction layer, since it involves a read, followed by a computation, followed by a write.
Mitigation
Use a mutex if available, but be sure to avoid related weaknesses such as CWE-412.
Mitigation
Avoid double-checked locking (CWE-609) and other implementation errors that arise when trying to avoid the overhead of synchronization.
Mitigation
Disable interrupts or signals over critical parts of the code, but also make sure that the code does not go into a large or infinite loop.
Mitigation
Use the volatile type modifier for critical variables to avoid unexpected compiler optimization or reordering. This does not necessarily solve the synchronization problem, but it can help.
Mitigation MIT-17
Strategy: Environment Hardening
Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.
CAPEC-26: Leveraging Race Conditions
The adversary targets a race condition occurring when multiple processes access and manipulate the same resource concurrently, and the outcome of the execution depends on the particular order in which the access takes place. The adversary can leverage a race condition by "running the race", modifying the resource and modifying the normal execution flow. For instance, a race condition can occur while accessing a file: the adversary can trick the system by replacing the original file with their version and cause the system to read the malicious file.
CAPEC-29: Leveraging Time-of-Check and Time-of-Use (TOCTOU) Race Conditions
This attack targets a race condition occurring between the time of check (state) for a resource and the time of use of a resource. A typical example is file access. The adversary can leverage a file access race condition by "running the race", meaning that they would modify the resource between the first time the target program accesses the file and the time the target program uses the file. During that period of time, the adversary could replace or modify the file, causing the application to behave unexpectedly.