Shared Mutable State

ID

python.shared_mutable_state

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Synchronization

Language

Python

Tags

ASVS50:v15.4.1, CWE:362, CWE:567, NIST.SP.800-53, PCI-DSS:6.5.6

Description

Detects shared mutable state — module-level globals, class attributes, and attributes of an instance a module builds at import — that is written outside initialization code. A module is imported once per process, so this state is read and written by every request a threaded server (a WSGI server, FastAPI’s threadpool for sync endpoints) runs at the same time, creating potential race conditions.

When the state is assigned a value read from a function parameter, concurrent requests hand one caller’s data to another, and that finding is raised to high. When it is a counter or a collection written concurrently, updates are lost.

One issue is reported per piece of state, at its worst write.

Rationale

A module-level dictionary written by a view:

HITS = {}

def record_hit(request, page):
    HITS[page] = 1          # every concurrent request writes the same dict

A counter rebound through global:

_served = 0

def serve(request):
    global _served
    _served += 1            # read-modify-write is not atomic

An instance built once at import, written per request:

class OrderStore:
    def __init__(self):
        self.orders = {}

    def save(self, number, order):
        self.orders[number] = order

order_store = OrderStore()  # one instance for the whole process

Remediation

  1. Eliminate Shared State: keep request data in arguments and return values, in the request, or in a contextvars.ContextVar.

  2. Use Thread-Safe Types: queue.Queue and collections.deque for queues, threading.local for per-thread values, a cache library for caches.

  3. Synchronize Access: guard every access with the same lock:

    _lock = threading.Lock()
    HITS = {}
    
    def record_hit(request, page):
        with _lock:
            HITS[page] = 1

Not Reported

  • A thread-safe value (queue.Queue, collections.deque, threading.local, ContextVar, a lock).

  • A write inside with a lock the module, the class or the instance holds, or after its acquire().

  • A lazy initialization, if x is None: x = …, whose value is not a parameter.

  • A write at import time, in init and the other initialization methods, in AppConfig.ready(), an application factory (create_app), a lifespan, or a startup hook (@app.on_event("startup")).

  • A helper that only initialization calls, such as a registration method called from init.

  • A call named like a mutator on a value that is not a collection (cache.set(…), KEY.replace(…)), and an instance a request builds for itself.

  • A function memoized with functools.lru_cache: the cache belongs to the decorator.

  • A test source.

© 2026 Xygeni. All rights reserved