Shared Mutable State

ID

kotlin.shared_mutable_state

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Synchronization

Language

Kotlin

Tags

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

Description

Detects shared mutable state — properties of an object, of a companion object, of the file itself, or of a singleton bean — that are written outside initialization code. State that lives once per process is accessed and modified by concurrent requests, creating potential race conditions.

When a property 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 the property is a counter or a mutable collection written concurrently, updates are lost and the collection can corrupt.

One issue is reported per property, at its worst write.

Rationale

A Kotlin object and a companion object exist once per process, and so does a property declared at the top level of a file. Spring components annotated with @Service, @Component, @Repository or @RestController are instantiated once per application and shared across all requests. Every concurrent request thread reads and writes the same property.

A property holding request data:

@Service
class UserService {
    private var currentUser: String? = null  // Shared across all requests

    fun handleRequest(userId: String) {
        currentUser = userId
        notifyUser(currentUser)  // May be a different user due to a concurrent request
    }
}

A registry in an object, written on every call:

object HitRegistry {
    private val hits = HashMap<String, Int>()

    fun record(page: String) {
        hits[page] = 1  // HashMap is not safe for concurrent writes
    }
}

Remediation

  1. Eliminate Shared State: Move request-specific data to function parameters, return values, or request-scoped beans (@RequestScope).

  2. Use Thread-Safe Types: For state that truly lives at the application level, use AtomicInteger, AtomicLong or LongAdder for counters and ConcurrentHashMap for caches.

  3. Synchronize Access: If shared state is unavoidable, guard every access with the same lock:

    private val lock = ReentrantLock()
    private val cache = HashMap<String, String>()
    
    fun updateCache(key: String, value: String) = lock.withLock {
        cache[key] = value
    }

Not Reported

  • A thread-safe type (ConcurrentHashMap, AtomicLong, ThreadLocal…), declared or constructed, or a value built by a synchronizing factory (Collections.synchronizedMap).

  • A write inside synchronized(…) { }, lock.withLock { } or withPermit { }, in a @Synchronized function, or after a lock() on a lock property.

  • A bean with a request, session or prototype scope (@RequestScope, @Scope("request")).

  • An injected property (@Autowired, @Value, @Inject…) and a delegated one (by lazy { … }).

  • A lazy initialisation, if (x == null) x = …, whose value is not a function parameter.

  • A write in an init block, a constructor, a property initializer, @PostConstruct, afterPropertiesSet, a start-up @EventListener, or a @ConfigurationProperties class.

  • An injection point: an @Autowired or @Value function, a container callback such as setApplicationContext, or a plain property setter that is not a request handler.

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

  • A call named like a mutator on a property that is not a mutable collection (redis.set(…), NAME.replace(…)).

  • A test source.

© 2026 Xygeni. All rights reserved