Incorrect request error handling triggers server crash in Vapor
Description
Vapor HTTP web framework versions prior to 4.84.2 are vulnerable to a denial of service attack due to mishandling of HTTP 1.x parse errors, causing server crash.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Vapor HTTP web framework versions prior to 4.84.2 are vulnerable to a denial of service attack due to mishandling of HTTP 1.x parse errors, causing server crash.
Vulnerability
Vapor, a Swift HTTP web framework, contains a denial of service vulnerability in its HTTP1 error handler. Instead of forwarding HTTP parsing errors through the pipeline, the handler closes the connection, which triggers a precondition failure in swift-nio and crashes the server [1][2][3].
Exploitation
An attacker can exploit this by sending a malformed HTTP 1.x request to any Vapor server running an affected version. No authentication or special network position is required; the request is simply parsed and the error causes the handler to misbehave [3].
Impact
The vulnerability results in immediate termination of the server process, causing a denial of service. There is no risk of data leakage or unauthorized code execution, as the crash is an explicit assertion failure. Service can be restored by restarting the server [3].
Mitigation
The issue is fixed in Vapor release 4.84.2. Users should upgrade to this version or later. No workarounds are available [1][3].
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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/vapor/vaporSwiftURL | >= 4.83.2, < 4.84.2 | 4.84.2 |
Affected products
2Patches
1090464a654b0Merge pull request from GHSA-3mwq-h3g6-ffhm
4 files changed · +6 −73
Sources/Vapor/HTTP/Server/HTTPServerHandler.swift+1 −1 modified@@ -71,7 +71,7 @@ final class HTTPServerHandler: ChannelInboundHandler, RemovableChannelHandler { self.logger.trace("HTTP handler will no longer respect keep-alive") self.isShuttingDown = true default: - self.logger.trace("Unhandled user event: \(event)") + context.fireUserInboundEventTriggered(event) } } }
Sources/Vapor/HTTP/Server/HTTPServerRequestDecoder.swift+5 −1 modified@@ -133,6 +133,10 @@ final class HTTPServerRequestDecoder: ChannelDuplexHandler, RemovableChannelHand default: break } + + if error is HTTPParserError { + self.logger.debug("Invalid HTTP request, will close connection: \(String(reflecting: error))") + } context.fireErrorCaught(error) } @@ -218,7 +222,7 @@ final class HTTPServerRequestDecoder: ChannelDuplexHandler, RemovableChannelHand context.fireUserInboundEventTriggered(event) } default: - self.logger.trace("Unhandled user event: \(event)") + context.fireUserInboundEventTriggered(event) } } }
Sources/Vapor/HTTP/Server/HTTPServer.swift+0 −70 modified@@ -452,73 +452,6 @@ private final class HTTPServerConnection: Sendable { } } -/// A simple channel handler that catches errors emitted by parsing HTTP requests -/// and sends 400 Bad Request responses. -/// -/// This channel handler provides the basic behaviour that the majority of simple HTTP -/// servers want. This handler does not suppress the parser errors: it allows them to -/// continue to pass through the pipeline so that other handlers (e.g. logging ones) can -/// deal with the error. -/// -/// adapted from: https://github.com/apple/swift-nio/blob/00341c92770e0a7bebdc5fda783f08765eb3ff56/Sources/NIOHTTP1/HTTPServerProtocolErrorHandler.swift -final class HTTP1ServerErrorHandler: ChannelDuplexHandler, RemovableChannelHandler { - typealias InboundIn = Never - typealias InboundOut = Never - typealias OutboundIn = HTTPServerResponsePart - typealias OutboundOut = HTTPServerResponsePart - let logger: Logger - private var hasUnterminatedResponse: Bool = false - - init(logger: Logger) { - self.logger = logger - } - - func errorCaught(context: ChannelHandlerContext, error: Error) { - if let error = error as? HTTPParserError { - self.makeHTTPParserErrorResponse(context: context, error: error) - } - - // Now pass the error on in case someone else wants to see it. - // In the Vapor ChannelPipeline the connection will eventually - // be closed by the NIOCloseOnErrorHandler - context.fireErrorCaught(error) - } - - private func makeHTTPParserErrorResponse(context: ChannelHandlerContext, error: HTTPParserError) { - // Any HTTPParserError is automatically fatal, and we don't actually need (or want) to - // provide that error to the client: we just want to inform them something went wrong - // and then close off the pipeline. However, we can only send an - // HTTP error response if another response hasn't started yet. - // - // A side note here: we cannot block or do any delayed work. - // The channel might be closed right after we return from this function. - if !self.hasUnterminatedResponse { - self.logger.debug("Bad Request - Invalid HTTP: \(error)") - let headers = HTTPHeaders([("Connection", "close"), ("Content-Length", "0")]) - let head = HTTPResponseHead(version: .http1_1, status: .badRequest, headers: headers) - context.write(self.wrapOutboundOut(.head(head)), promise: nil) - context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) - } - } - - public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) { - let res = self.unwrapOutboundIn(data) - switch res { - case .head(let head) where head.isInformational: - precondition(!self.hasUnterminatedResponse) - case .head: - precondition(!self.hasUnterminatedResponse) - self.hasUnterminatedResponse = true - case .body: - precondition(self.hasUnterminatedResponse) - case .end: - precondition(self.hasUnterminatedResponse) - self.hasUnterminatedResponse = false - } - context.write(data, promise: promise) - } -} - extension HTTPResponseHead { /// Determines if the head is purely informational. If a head is informational another head will follow this /// head eventually. @@ -608,9 +541,6 @@ extension ChannelPipeline { break } - let errorHandler = HTTP1ServerErrorHandler(logger: configuration.logger) - handlers.append(errorHandler) - // add NIO -> HTTP response encoder let serverResEncoder = HTTPServerResponseEncoder( serverHeader: configuration.serverName,
Tests/VaporTests/PipelineTests.swift+0 −1 modified@@ -138,7 +138,6 @@ final class PipelineTests: XCTestCase { } } XCTAssertEqual(channel.isActive, false) - try XCTAssertContains(channel.readOutbound(as: ByteBuffer.self)?.string, "HTTP/1.1 400 Bad Request") try XCTAssertNil(channel.readOutbound(as: ByteBuffer.self)?.string) }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-3mwq-h3g6-ffhmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-44386ghsaADVISORY
- github.com/vapor/vapor/commit/090464a654b03148b139a81f8f5ac63b0856f6f3ghsax_refsource_MISCWEB
- github.com/vapor/vapor/releases/tag/4.84.2ghsax_refsource_MISCWEB
- github.com/vapor/vapor/security/advisories/GHSA-3mwq-h3g6-ffhmghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.