Skip to content

VNX-SWIFT-006: Swift non-cryptographic PRNG (libc rand/random/drand48, GameplayKit) used for a security value

Detects the libc rand/random/drand48 family and GameplayKit random sources (directly or through a source variable) used to produce a token, nonce, salt, key, IV, PIN, OTP or password in Swift; arc4random, SystemRandomNumberGenerator and .random(in:) are CSPRNG-backed on Apple platforms and are not reported.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-SWIFT-006
Severity High
SARIF level error
Kind sast
Languages Swift
CWE CWE-338
CAPEC CAPEC-112
ATT&CK T1600
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags weak-random cryptography swift ios
Source internal/sast/rules/vnx-swift-006.rego

Overview

This rule flags calls to non-cryptographic generators in Swift: the libc rand(), random(), drand48(), lrand48(), mrand48() and erand48/nrand48/jrand48, and GameplayKit’s random sources and distributions (GKRandomSource, GKARC4RandomSource, GKLinearCongruentialRandomSource, GKMersenneTwisterRandomSource, GKRandomDistribution, GKGaussianDistribution, GKShuffledDistribution), which Apple documents as unsuitable for cryptography. A call is reported only when its result is a security value: the line (or a let/var opened up to three lines above it) assigns a name ending with token, nonce, salt, secret, password, OTP, CSRF, session id, challenge, API key, a *Key, IV or PIN, or the enclosing function or type is named for one (generateToken, makeNonce, enum OTP).

Not reported: arc4random(), arc4random_uniform(), arc4random_buf(), SystemRandomNumberGenerator and Int.random(in:), which are backed by the system CSPRNG on Apple platforms; SecRandomCopyBytes and CryptoKit; generators used for shuffling, jitter, delays or cache keys; seeding calls on their own; string literals, comments and test files.

This maps to CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).

Why This Matters

Security properties of tokens, keys, nonces, and salts depend entirely on their unpredictability. A PRNG that is predictable, or one whose internal state can be reconstructed from observed outputs, allows an attacker to enumerate possible values, dramatically reducing the search space for brute-force attacks.

On Apple platforms arc4random and its variants use a ChaCha20-based CSPRNG seeded by the kernel, and Swift’s SystemRandomNumberGenerator (used by Int.random(in:), shuffled() and friends) is documented as cryptographically secure there. rand(), random() and the drand48 family are linear generators whose state can be recovered from a handful of outputs, and the GameplayKit sources are deterministic, seedable generators meant for games.

The attack is most impactful when a predictable PRNG is used to generate password reset tokens, OAuth state parameters, CSRF tokens, one-time passwords, or symmetric encryption IVs. If the attacker can observe a few generated values (e.g., by triggering multiple password resets), they may be able to reconstruct the generator’s state and predict all future values, compromising every account that requests a reset during the window.

Apple’s SecRandomCopyBytes and the CryptoKit framework’s SymmetricKey and Nonce types use the OS CSPRNG (/dev/random backed by the Secure Enclave) and are the correct choice for all security-sensitive random data generation on Apple platforms.

What Gets Flagged

// FLAGGED: libc rand() for a reset token
let resetToken = String(rand(), radix: 16)

// FLAGGED: drand48() for a salt
let salt = drand48()

// FLAGGED: GameplayKit for a one-time code
let otp = GKRandomSource.sharedRandom().nextInt(upperBound: 1_000_000)

// FLAGGED: rand() inside a key generator
func generateSessionKey() -> [UInt8] {
    var bytes = [UInt8](repeating: 0, count: 32)
    for i in 0..<bytes.count {
        bytes[i] = UInt8(truncatingIfNeeded: rand())
    }
    return bytes
}

// FLAGGED: a GameplayKit source held in a property, then drawn from
private let source = GKMersenneTwisterRandomSource(seed: 1337)
let otp = source.nextInt(upperBound: 1_000_000)

// NOT FLAGGED: jitter inside a token-refresh function
let jitter = Double(random() % 30)

// NOT FLAGGED: CSPRNG-backed on Apple platforms
let nonce = arc4random_uniform(UInt32.max)
let pin = Int.random(in: 0..<10_000)

Remediation

  1. Use SecRandomCopyBytes for raw random bytes. This function fills a buffer with cryptographically secure random data using the hardware RNG.

    // SAFE: cryptographically secure token using SecRandomCopyBytes
    import Security
    
    func generateSecureToken(length: Int = 32) throws -> String {
        var bytes = [UInt8](repeating: 0, count: length)
        let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
        guard status == errSecSuccess else {
            throw CryptoError.randomGenerationFailed(status)
        }
        return bytes.map { String(format: "%02x", $0) }.joined()
    }
  2. Use CryptoKit for symmetric keys, nonces, and IVs. CryptoKit’s types generate secure random values internally and enforce correct sizes for the chosen algorithm.

    // SAFE: AES-GCM encryption with CryptoKit-generated key and nonce
    import CryptoKit
    
    func encryptData(_ plaintext: Data, using key: SymmetricKey) throws -> (ciphertext: Data, tag: Data, nonce: AES.GCM.Nonce) {
        let nonce = AES.GCM.Nonce()  // Cryptographically secure, correct size
        let sealed = try AES.GCM.seal(plaintext, using: key, nonce: nonce)
        return (sealed.ciphertext, sealed.tag, nonce)
    }
    
    // Generate a 256-bit key
    let key = SymmetricKey(size: .bits256)  // Cryptographically secure
  3. Use UUID() only for identifiers. It is random but has a fixed format and only 122 random bits; for a secret value, generate bytes with SecRandomCopyBytes or CryptoKit.

  4. Audit all token and key generation code. Search for rand(, random(, drand48( and GK*RandomSource and replace any that generate security-relevant values.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-swift-006
// nosec vnx-swift-006

Or record an ignore rule: vulnetix ignore add --rule vnx-swift-006 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-swift-006.rego, cli/internal/sast/docs/vnx-swift-006.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.