one actor's data, served to another

Cross-Boundary State Exposure

A singleton, module global, or pooled object is reused across requests, threads, or sessions without resetting its per-actor fields. Whoever touched it last leaves their data for whoever touches it next — so one user is handed another's state.

Per-actor state parked in a shared singleton, global, or pool bleeds across the boundary that was meant to keep actors apart.

01in the wild

In the wild

Shared Singleton Holds Per-Request State

A single shared object with mutable, per-call state lets concurrent actors overwrite and read each other's data.

example.java
// SMELL: one shared SimpleDateFormat for every thread
static final SimpleDateFormat FMT = new SimpleDateFormat("yyyy-MM-dd");
String render(Date d) { return FMT.format(d); }   // FMT keeps a mutable
                                                  // Calendar -> threads read
                                                  // each other's half-write

// RIGHT: give each call its own formatter (no shared state)
String render(Date d) {
    return new SimpleDateFormat("yyyy-MM-dd").format(d);
}
SimpleDateFormat is not thread-safe; a static instance shares one mutable Calendar, so concurrent threads corrupt each other's formatting.
// observed
shared:   garbled / interleaved dates under load
per-call: every thread formats its own date correctly
example.py
# SMELL: a module-level slot reused by every concurrent request
_current_user = None
def set_user(u):
    global _current_user
    _current_user = u
def audit(action):
    log(_current_user, action)   # another request may have overwritten it

# RIGHT: carry per-request state with the request, not in a global
def audit(user, action):
    log(user, action)            # state travels with the caller
A module global is one slot shared by every concurrent request; the last writer wins and everyone else reads its value.
// observed
global:   request B's action logged under user A
per-call: each request keeps its own user

Pooled Object Reused Without Reset

An object returned to a pool still carrying its last user's fields hands that data to the next borrower.

example.py
# SMELL: a pooled context handed out without clearing the last user
ctx = pool.acquire()             # still holds the previous request's user
ctx.run(request)                 # ...so this request acts as the wrong user
pool.release(ctx)

# RIGHT: reset the per-actor state on borrow, so nothing crosses over
ctx = pool.acquire()
ctx.reset()                      # wipe identity before reuse
ctx.user = request.user
ctx.run(request)
pool.release(ctx)
A pool recycles objects for speed; if per-actor fields are not reset on borrow, the previous borrower's identity leaks into the next request.
// observed
no reset: request acts under the prior user's identity
reset:    each borrow starts from a clean object
example.java
// SMELL: a ThreadLocal left set when the thread returns to the pool
static final ThreadLocal<User> CURRENT = new ThreadLocal<>();
void handle(Req r) {
    CURRENT.set(r.user());
    process();                   // pooled thread reused -> CURRENT still set
}                                // for the *next* request on this thread

// RIGHT: clear the ThreadLocal in a finally, before the thread is recycled
void handle(Req r) {
    CURRENT.set(r.user());
    try { process(); }
    finally { CURRENT.remove(); }
}
Servlet/worker threads are pooled and reused; a ThreadLocal left set bleeds into the next request that lands on the same thread.
// observed
leak:  next request inherits the prior user via ThreadLocal
remove: state cleared before the thread is reused
02cross-pollination

Where this compounds

03weakness catalog

Mapped weaknesses (CWE)

On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.