Skip to content

VNX-SWIFT-004: Swift TLS certificate validation disabled in URLSession or AlamoFire

Detects Swift code that accepts a server certificate without validating it: URLCredential(trust:) built without evaluating or pinning the trust, Alamofire DisabledTrustEvaluator/.disableEvaluation, kCFStreamSSLValidatesCertificateChain false, setAllowsAnyHTTPSCertificate, Starscream disableSSLCertValidation/allowSelfSigned, and a Network.framework verify block that always completes true.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-SWIFT-004
Severity High
SARIF level error
Kind sast
Languages Swift
CWE CWE-295
CAPEC CAPEC-94
ATT&CK T1557
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 tls certificate-validation swift ios
Source internal/sast/rules/vnx-swift-004.rego

Overview

This rule reports Swift code that accepts a TLS server certificate without validating it:

  1. URLCredential(trust:) built in a function that never evaluates or pins the trust before it (no SecTrustEvaluateWithError/SecTrustEvaluate, evaluator.evaluate(...), certificate or public-key comparison, or call to a validate/verify/check/pin helper). This is how a URLSession or NSURLConnection auth-challenge delegate trusts every certificate. A host-name comparison is not validation.
  2. Alamofire DisabledTrustEvaluator() / DisabledEvaluator() and the Alamofire 4 ServerTrustPolicy .disableEvaluation.
  3. kCFStreamSSLValidatesCertificateChain set to false on a stream.
  4. NSURLRequest.setAllowsAnyHTTPSCertificate(true, forHost:).
  5. A sec_protocol_options_set_verify_block whose block completes with true without evaluating the trust.

It does not report URLCredential(trust:) after the trust was evaluated or pinned, URLCredential(user:password:), .performDefaultHandling, pinned or default Alamofire evaluators, the evaluator class declarations, string literals, comments and test files.

This maps to CWE-295: Improper Certificate Validation.

Why This Matters

Disabling TLS certificate validation eliminates the only mechanism that proves the server the application is communicating with is who it claims to be. On any untrusted network, public Wi-Fi, hotel networks, carrier networks with traffic inspection appliances, an attacker can perform a man-in-the-middle (MITM) attack by presenting their own certificate to the client. With validation disabled, the client accepts this certificate and the attacker receives all traffic in cleartext: credentials, session tokens, payment data, health information, and any other sensitive content.

This pattern is extremely common in development codebases. Developers disable validation to work with self-signed certificates on local test servers and forget to re-enable it before shipping. Tools such as Burp Suite and Charles Proxy are explicitly designed to exploit this class of vulnerability to intercept mobile app traffic, and penetration testers routinely discover it in production applications.

Certificate pinning, associating the server’s specific public key with the application, provides an additional layer of defence beyond standard chain validation. Even if an attacker obtains a certificate from a trusted CA, pinning will reject it unless the public key matches the pinned value.

What Gets Flagged

// FLAGGED: URLCredential(trust:) unconditionally accepts any certificate
func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) // FLAGGED
    completionHandler(.useCredential, credential)
}

// FLAGGED: completion handler accepts credential without verifying serverTrust
func urlSession(_ session: URLSession,
                task: URLSessionTask,
                didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}

// FLAGGED: Starscream WebSocket accepting any or self-signed certificates
socket.disableSSLCertValidation = true
let pinner = FoundationSecurity(allowSelfSigned: true)

// FLAGGED: Alamofire server trust evaluation disabled
let manager = Session(serverTrustManager: ServerTrustManager(evaluators: [
    "api.example.com": DisabledTrustEvaluator()  // FLAGGED
]))

Remediation

  1. Perform proper server trust evaluation in URLSession authentication challenge delegates. Validate the certificate chain against system-trusted roots before calling completionHandler(.useCredential, ...).

    // SAFE: proper server trust evaluation
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
    
        // Evaluate the server trust using system policy
        var error: CFError?
        let isValid = SecTrustEvaluateWithError(serverTrust, &error)
    
        if isValid {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
  2. Use Alamofire’s PinnedCertificatesTrustEvaluator or PublicKeysTrustEvaluator for certificate pinning. This validates both the chain and the specific certificate or key.

    // SAFE: Alamofire with certificate pinning
    let evaluators: [String: ServerTrustEvaluating] = [
        "api.example.com": PinnedCertificatesTrustEvaluator(
            certificates: Bundle.main.af.certificates,
            acceptSelfSignedCertificates: false,
            performDefaultValidation: true,
            validateHost: true
        )
    ]
    let session = Session(serverTrustManager: ServerTrustManager(evaluators: evaluators))
  3. Remove test-only validation bypasses before merging to main. Use build flags or separate test configurations so that certificate validation bypasses used for local development are never included in production builds.

    // SAFE: bypass only in debug builds
    #if DEBUG
    let evaluators: [String: ServerTrustEvaluating] = [
        "localhost": DisabledTrustEvaluator()
    ]
    #else
    let evaluators: [String: ServerTrustEvaluating] = [
        "api.example.com": PinnedCertificatesTrustEvaluator()
    ]
    #endif
  4. Enable App Transport Security (ATS). Ensure NSAllowsArbitraryLoads is not set to true in Info.plist. ATS enforces minimum TLS requirements at the OS level as an additional safety net.

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-004
// nosec vnx-swift-004

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

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