Shared Mutable State

ID

swift.shared_mutable_state

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Synchronization

Language

Swift

Tags

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

Description

Detects shared mutable state — global variables, static properties, and the properties of the one instance a singleton holds — that is written outside initialization code. A server such as Vapor runs its route handlers concurrently on its event loops, so this state is read and written by many requests at the same time, creating potential race conditions.

A singleton is a class with a static let shared = Registry(), or a global constant holding an instance of a class the file declares.

When the state is assigned a value read from a function or closure 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 and the collection can be corrupted.

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

Rationale

A global array a handler appends to:

var recentSearches: [String] = []

func remember(_ req: Request) {
    recentSearches.append(req.url.string)   // every concurrent request writes the same array
}

A static counter:

enum HitCounter {
    static var hits = 0
}

app.get("hit") { req -> String in
    HitCounter.hits += 1                     // read-modify-write is not atomic
    return "ok"
}

A singleton written per request:

final class SessionCache {
    static let shared = SessionCache()
    var sessions: [String: String] = [:]

    func put(_ id: String, user: String) {
        sessions[id] = user                  // one instance for the whole process
    }
}

Remediation

  1. Eliminate Shared State: keep request data in the request (req.storage), in arguments and return values, or in a service built per request.

  2. Use an Actor: an actor isolates its state, and its callers reach it one at a time:

    actor SessionStore {
        var sessions: [String: String] = [:]
        func put(_ id: String, user: String) { sessions[id] = user }
    }
  3. Use Thread-Safe Types or a Lock: ManagedAtomic for counters, NIOLockedValueBox or Mutex for values, or a lock held around every access (lock.withLock { … }).

Not Reported

  • The state of an actor, and state isolated to a global actor (@MainActor) or local to a task (@TaskLocal).

  • A thread-safe holder (ManagedAtomic, NIOLockedValueBox, Mutex, OSAllocatedUnfairLock).

  • A write inside a closure a lock or a serial queue runs (lock.withLock { … }, queue.sync { … }), or after the function takes a lock (lock.lock()).

  • A lazy initialization, if x == nil { x = … } or after if let v = x { return v }, whose value is not a parameter.

  • A write in an initializer, a deinitializer, or a function that boots the application (configure(_:)).

  • Test code, and client code: a file importing SwiftUI, UIKit or AppKit serves one user.

© 2026 Xygeni. All rights reserved