VYPR
High severityNVD Advisory· Published Jan 18, 2023· Updated Apr 3, 2025

CVE-2023-0040

CVE-2023-0040

Description

Async HTTP Client before 1.13.2 is vulnerable to CRLF injection via insufficient validation of HTTP header values, allowing attackers to inject headers or requests.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Async HTTP Client before 1.13.2 is vulnerable to CRLF injection via insufficient validation of HTTP header values, allowing attackers to inject headers or requests.

Vulnerability

Overview

CVE-2023-0040 describes a CRLF injection vulnerability in the Async HTTP Client library for Swift, affecting versions prior to 1.13.2. The root cause is insufficient validation of HTTP header field values before they are sent over the network [1][2]. When untrusted data—such as usernames from a database—is placed into header fields without prior sanitization, an attacker can inject carriage return and line feed (CRLF) sequences.

Exploitation

Prerequisites

Exploitation requires the application to pass untrusted data into HTTP header values. No authentication is needed if the attacker can control the input that ends up in a header. The attacker does not need to be on the network path; the injection occurs at the client side, and the crafted request is sent to the remote server [2].

Impact

A successful CRLF injection allows an attacker to add arbitrary HTTP headers or even split the request into multiple HTTP requests (HTTP request smuggling). This can cause the server to interpret the request differently than intended, leading to logical errors, cache poisoning, or other misbehaviors. The advisory notes that data disclosure is unlikely, but the integrity of request handling can be compromised [2].

Mitigation

The vulnerability is fixed in Async HTTP Client version 1.13.2 [4]. The fix includes validation of header field names and values to reject invalid characters, as shown in the commit that added tests for rejecting invalid characters in header field names [3]. Users should upgrade to 1.13.2 or later and ensure that any untrusted data used in headers is properly sanitized.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/swift-server/async-http-clientSwiftURL
>= 1.13.0, < 1.13.21.13.2
github.com/swift-server/async-http-clientSwiftURL
>= 1.10.0, < 1.12.11.12.1
github.com/swift-server/async-http-clientSwiftURL
>= 1.5.0, < 1.9.11.9.1
github.com/swift-server/async-http-clientSwiftURL
< 1.4.11.4.1

Affected products

2

Patches

1
7f05a8da46cc

Merge pull request from GHSA-v3r5-pjpm-mwgq

https://github.com/swift-server/async-http-clientCory BenfieldJan 17, 2023via ghsa
4 files changed · +188 0
  • Sources/AsyncHTTPClient/HTTPClient.swift+5 0 modified
    @@ -1032,6 +1032,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
             case uncleanShutdown
             case traceRequestWithBody
             case invalidHeaderFieldNames([String])
    +        case invalidHeaderFieldValues([String])
             case bodyLengthMismatch
             case writeAfterRequestSent
             @available(*, deprecated, message: "AsyncHTTPClient now silently corrects invalid headers.")
    @@ -1100,6 +1101,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
                 return "Trace request with body"
             case .invalidHeaderFieldNames:
                 return "Invalid header field names"
    +        case .invalidHeaderFieldValues:
    +            return "Invalid header field values"
             case .bodyLengthMismatch:
                 return "Body length mismatch"
             case .writeAfterRequestSent:
    @@ -1166,6 +1169,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
         public static let traceRequestWithBody = HTTPClientError(code: .traceRequestWithBody)
         /// Header field names contain invalid characters.
         public static func invalidHeaderFieldNames(_ names: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldNames(names)) }
    +    /// Header field values contain invalid characters.
    +    public static func invalidHeaderFieldValues(_ values: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldValues(values)) }
         /// Body length is not equal to `Content-Length`.
         public static let bodyLengthMismatch = HTTPClientError(code: .bodyLengthMismatch)
         /// Body part was written after request was fully sent.
    
  • Sources/AsyncHTTPClient/RequestValidation.swift+51 0 modified
    @@ -21,6 +21,7 @@ extension HTTPHeaders {
             bodyLength: RequestBodyLength
         ) throws -> RequestFramingMetadata {
             try self.validateFieldNames()
    +        try self.validateFieldValues()
     
             if case .TRACE = method {
                 switch bodyLength {
    @@ -80,6 +81,56 @@ extension HTTPHeaders {
             }
         }
     
    +    private func validateFieldValues() throws {
    +        let invalidValues = self.compactMap { _, value -> String? in
    +            let satisfy = value.utf8.allSatisfy { char -> Bool in
    +                /// Validates a byte of a given header field value against the definition in RFC 9110.
    +                ///
    +                /// The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
    +                /// characters as the following:
    +                ///
    +                /// ```
    +                /// field-value    = *field-content
    +                /// field-content  = field-vchar
    +                ///                  [ 1*( SP / HTAB / field-vchar ) field-vchar ]
    +                /// field-vchar    = VCHAR / obs-text
    +                /// obs-text       = %x80-FF
    +                /// ```
    +                ///
    +                /// Additionally, it makes the following note:
    +                ///
    +                /// "Field values containing CR, LF, or NUL characters are invalid and dangerous, due to the
    +                /// varying ways that implementations might parse and interpret those characters; a recipient
    +                /// of CR, LF, or NUL within a field value MUST either reject the message or replace each of
    +                /// those characters with SP before further processing or forwarding of that message. Field
    +                /// values containing other CTL characters are also invalid; however, recipients MAY retain
    +                /// such characters for the sake of robustness when they appear within a safe context (e.g.,
    +                /// an application-specific quoted string that will not be processed by any downstream HTTP
    +                /// parser)."
    +                ///
    +                /// As we cannot guarantee the context is safe, this code will reject all ASCII control characters
    +                /// directly _except_ for HTAB, which is explicitly allowed.
    +                switch char {
    +                case UInt8(ascii: "\t"):
    +                    // HTAB, explicitly allowed.
    +                    return true
    +                case 0...0x1f, 0x7F:
    +                    // ASCII control character, forbidden.
    +                    return false
    +                default:
    +                    // Printable or non-ASCII, allowed.
    +                    return true
    +                }
    +            }
    +
    +            return satisfy ? nil : value
    +        }
    +
    +        guard invalidValues.count == 0 else {
    +            throw HTTPClientError.invalidHeaderFieldValues(invalidValues)
    +        }
    +    }
    +
         private mutating func setTransportFraming(
             method: HTTPMethod,
             bodyLength: RequestBodyLength
    
  • Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift+128 0 modified
    @@ -597,6 +597,134 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
                 XCTAssertEqual(body, ByteBuffer(string: "1234"))
             }
         }
    +
    +    func testRejectsInvalidCharactersInHeaderFieldNames_http1() {
    +        self._rejectsInvalidCharactersInHeaderFieldNames(mode: .http1_1(ssl: true))
    +    }
    +
    +    func testRejectsInvalidCharactersInHeaderFieldNames_http2() {
    +        self._rejectsInvalidCharactersInHeaderFieldNames(mode: .http2(compress: false))
    +    }
    +
    +    private func _rejectsInvalidCharactersInHeaderFieldNames(mode: HTTPBin<HTTPBinHandler>.Mode) {
    +        guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
    +        XCTAsyncTest {
    +            let bin = HTTPBin(mode)
    +            defer { XCTAssertNoThrow(try bin.shutdown()) }
    +            let client = makeDefaultHTTPClient()
    +            defer { XCTAssertNoThrow(try client.syncShutdown()) }
    +            let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
    +
    +            // The spec in [RFC 9110](https://httpwg.org/specs/rfc9110.html#fields.values) defines the valid
    +            // characters as the following:
    +            //
    +            // ```
    +            // field-name     = token
    +            //
    +            // token          = 1*tchar
    +            //
    +            // tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
    +            //                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
    +            //                / DIGIT / ALPHA
    +            //                ; any VCHAR, except delimiters
    +            let weirdAllowedFieldName = "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    +
    +            var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get")
    +            request.headers.add(name: weirdAllowedFieldName, value: "present")
    +
    +            // This should work fine.
    +            guard let response = await XCTAssertNoThrowWithResult(
    +                try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)
    +            ) else {
    +                return
    +            }
    +
    +            XCTAssertEqual(response.status, .ok)
    +
    +            // Now, let's confirm all other bytes are rejected. We want to stay within the ASCII space as the HTTPHeaders type will forbid anything else.
    +            for byte in UInt8(0)...UInt8(127) {
    +                // Skip bytes that we already believe are allowed.
    +                if weirdAllowedFieldName.utf8.contains(byte) {
    +                    continue
    +                }
    +                let forbiddenFieldName = weirdAllowedFieldName + String(decoding: [byte], as: UTF8.self)
    +
    +                var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get")
    +                request.headers.add(name: forbiddenFieldName, value: "present")
    +
    +                await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in
    +                    XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldNames([forbiddenFieldName]))
    +                }
    +            }
    +        }
    +    }
    +
    +    func testRejectsInvalidCharactersInHeaderFieldValues_http1() {
    +        self._rejectsInvalidCharactersInHeaderFieldValues(mode: .http1_1(ssl: true))
    +    }
    +
    +    func testRejectsInvalidCharactersInHeaderFieldValues_http2() {
    +        self._rejectsInvalidCharactersInHeaderFieldValues(mode: .http2(compress: false))
    +    }
    +
    +    private func _rejectsInvalidCharactersInHeaderFieldValues(mode: HTTPBin<HTTPBinHandler>.Mode) {
    +        guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
    +        XCTAsyncTest {
    +            let bin = HTTPBin(mode)
    +            defer { XCTAssertNoThrow(try bin.shutdown()) }
    +            let client = makeDefaultHTTPClient()
    +            defer { XCTAssertNoThrow(try client.syncShutdown()) }
    +            let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
    +
    +            // We reject all ASCII control characters except HTAB and tolerate everything else.
    +            let weirdAllowedFieldValue = "!\" \t#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
    +
    +            var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get")
    +            request.headers.add(name: "Weird-Value", value: weirdAllowedFieldValue)
    +
    +            // This should work fine.
    +            guard let response = await XCTAssertNoThrowWithResult(
    +                try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)
    +            ) else {
    +                return
    +            }
    +
    +            XCTAssertEqual(response.status, .ok)
    +
    +            // Now, let's confirm all other bytes in the ASCII range ar rejected
    +            for byte in UInt8(0)...UInt8(127) {
    +                // Skip bytes that we already believe are allowed.
    +                if weirdAllowedFieldValue.utf8.contains(byte) {
    +                    continue
    +                }
    +                let forbiddenFieldValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self)
    +
    +                var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get")
    +                request.headers.add(name: "Weird-Value", value: forbiddenFieldValue)
    +
    +                await XCTAssertThrowsError(try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)) { error in
    +                    XCTAssertEqual(error as? HTTPClientError, .invalidHeaderFieldValues([forbiddenFieldValue]))
    +                }
    +            }
    +
    +            // All the bytes outside the ASCII range are fine though.
    +            for byte in UInt8(128)...UInt8(255) {
    +                let evenWeirderAllowedValue = weirdAllowedFieldValue + String(decoding: [byte], as: UTF8.self)
    +
    +                var request = HTTPClientRequest(url: "https://localhost:\(bin.port)/get")
    +                request.headers.add(name: "Weird-Value", value: evenWeirderAllowedValue)
    +
    +                // This should work fine.
    +                guard let response = await XCTAssertNoThrowWithResult(
    +                    try await client.execute(request, deadline: .now() + .seconds(10), logger: logger)
    +                ) else {
    +                    return
    +                }
    +
    +                XCTAssertEqual(response.status, .ok)
    +            }
    +        }
    +    }
     }
     
     extension AsyncSequence where Element == ByteBuffer {
    
  • Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests+XCTest.swift+4 0 modified
    @@ -45,6 +45,10 @@ extension AsyncAwaitEndToEndTests {
                 ("testShutdown", testShutdown),
                 ("testCancelingBodyDoesNotCrash", testCancelingBodyDoesNotCrash),
                 ("testAsyncSequenceReuse", testAsyncSequenceReuse),
    +            ("testRejectsInvalidCharactersInHeaderFieldNames_http1", testRejectsInvalidCharactersInHeaderFieldNames_http1),
    +            ("testRejectsInvalidCharactersInHeaderFieldNames_http2", testRejectsInvalidCharactersInHeaderFieldNames_http2),
    +            ("testRejectsInvalidCharactersInHeaderFieldValues_http1", testRejectsInvalidCharactersInHeaderFieldValues_http1),
    +            ("testRejectsInvalidCharactersInHeaderFieldValues_http2", testRejectsInvalidCharactersInHeaderFieldValues_http2),
             ]
         }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.