From 37c70c60d1e03321818bbe9a961160e0ed82e955 Mon Sep 17 00:00:00 2001 From: Lloouujjiinn Date: Sat, 16 May 2026 08:25:06 +0300 Subject: [PATCH 1/3] Use strict Base64 decoding for JWT parts --- JWTDecode/JWTDecode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JWTDecode/JWTDecode.swift b/JWTDecode/JWTDecode.swift index e9aceee..b4b0f24 100644 --- a/JWTDecode/JWTDecode.swift +++ b/JWTDecode/JWTDecode.swift @@ -201,7 +201,7 @@ private func base64UrlDecode(_ value: String) -> Data? { let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0) base64 += padding } - return Data(base64Encoded: base64, options: .ignoreUnknownCharacters) + return Data(base64Encoded: base64) } private func decodeJWTPart(_ value: String) throws -> [String: any Sendable] { From 86c5595ec1b39662a20acf4026afa479d8d3d522 Mon Sep 17 00:00:00 2001 From: Lloouujjiinn Date: Sat, 16 May 2026 08:25:07 +0300 Subject: [PATCH 2/3] Avoid exposing JWT contents in error descriptions --- JWTDecode/JWTDecodeError.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/JWTDecode/JWTDecodeError.swift b/JWTDecode/JWTDecodeError.swift index d66a6b2..f08aa3a 100644 --- a/JWTDecode/JWTDecodeError.swift +++ b/JWTDecode/JWTDecodeError.swift @@ -29,12 +29,12 @@ public enum JWTDecodeError: LocalizedError, CustomDebugStringConvertible, Sendab /// - Important: You should avoid displaying the error description to the user, it's meant for **debugging** only. public var debugDescription: String { switch self { - case .invalidJSON(let value): - return "Failed to parse JSON from Base64URL value \(value)." - case .invalidPartCount(let jwt, let parts): - return "The JWT \(jwt) has \(parts) parts when it should have 3 parts." - case .invalidBase64URL(let value): - return "Failed to decode Base64URL value \(value)." + case .invalidJSON: + return "Failed to parse JSON from a Base64URL JWT part." + case .invalidPartCount(_, let parts): + return "The JWT has \(parts) parts when it should have 3 parts." + case .invalidBase64URL: + return "Failed to decode a Base64URL JWT part." case .claimDecodingFailed(let message): return "Failed to decode claim: \(message)" } From bd8aee43c44da837006925706228212d843073e1 Mon Sep 17 00:00:00 2001 From: Lloouujjiinn Date: Sat, 16 May 2026 08:25:47 +0300 Subject: [PATCH 3/3] Cover strict decoding and redacted error descriptions --- JWTDecodeTests/JWTDecodeSpec.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/JWTDecodeTests/JWTDecodeSpec.swift b/JWTDecodeTests/JWTDecodeSpec.swift index 14fa03c..4e19cd9 100644 --- a/JWTDecodeTests/JWTDecodeSpec.swift +++ b/JWTDecodeTests/JWTDecodeSpec.swift @@ -66,6 +66,13 @@ class JWTDecodeSpec: XCTestCase { } } + func testInvalidBase64WithIgnoredCharactersIsRejected() { + let jwtString = "eyJhbGciOiJIUzI1NiJ%.e30.SIGNATURE" + XCTAssertThrowsError(try decode(jwt: jwtString)) { error in + XCTAssertEqual(error as? JWTDecodeError, .invalidBase64URL("eyJhbGciOiJIUzI1NiJ%")) + } + } + func testRaiseExceptionWithInvalidJSONInJWT() { let jwtString = "HEADER.BODY.SIGNATURE" XCTAssertThrowsError(try decode(jwt: jwtString)) { error in @@ -80,6 +87,15 @@ class JWTDecodeSpec: XCTestCase { } } + func testErrorDescriptionsDoNotExposeJWTContents() { + let jwtString = "header.payload.signature" + let encodedPart = "eyJzdWIiOiJzZWNyZXQifQ" + + XCTAssertFalse(JWTDecodeError.invalidPartCount(jwtString, 3).localizedDescription.contains(jwtString)) + XCTAssertFalse(JWTDecodeError.invalidJSON(encodedPart).localizedDescription.contains(encodedPart)) + XCTAssertFalse(JWTDecodeError.invalidBase64URL(encodedPart).localizedDescription.contains(encodedPart)) + } + func testReturnHeader() { let jwtString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWIifQ.xXcD7WOvUDHJ94E6aVHYgXdsJHLl2oW7Z" + "Xm4QpVvXnY"