Feb 22, 2026·5 min read·11 visits
A critical authentication bypass affects 'jose-swift' versions <= 6.0.1. The library improperly handles JWTs with the 'alg': 'none' header, returning a successful verification result immediately without checking for a valid signature. This allows attackers to forge tokens with arbitrary privileges. The fix (v6.0.2) enforces algorithm blacklisting and signature presence checks.
In the world of JSON Web Tokens (JWT), the 'none' algorithm is the cryptographer's equivalent of a screen door on a submarine. It exists for debugging, yet it somehow keeps finding its way into production logic. This time, the victim is 'jose-swift', a popular library for JOSE standards in the Swift ecosystem. A critical logic flaw in the signature verification process allowed attackers to bypass authentication entirely by simply telling the server, 'Trust me, I'm unverified.' By manipulating the JWT header to specify the 'none' algorithm, an attacker could forge tokens with arbitrary claims—becoming admin, modifying user data, or accessing protected routes—without knowing the signing key.
In the realm of modern authentication, we rely heavily on the JSON Web Token (JWT). It’s the stateless passport of the web. The server signs it, hands it to the client, and says, 'Show me this later, and I'll know it's you.' The security of this entire system rests on one assumption: The cryptographic signature cannot be forged.
'jose-swift' is a comprehensive implementation of these standards for the Swift ecosystem, used by iOS, macOS, and server-side Swift developers to handle JWS (Signing) and JWE (Encryption). It handles the messy math so developers don't have to.
But what happens when the library decides to trust the metadata inside the token before verifying the token itself? It’s analogous to a bouncer checking ID, seeing a sticky note on it that says 'I am 21', and letting the person in without looking at the birth date. That is exactly what happened here. A logic flaw in the verification routine turned a robust cryptographic library into a rubber stamp for forgery.
The 'none' algorithm is a vestige of the original JWT specification, intended for situations where the integrity of the token is already guaranteed by the transport layer (like mutual TLS) or for debugging. In a 'none' token, the signature segment is empty. The token essentially says, 'I am unsigned.'
The vulnerability in jose-swift (GHSA-88Q6-JCJG-HVMW) is a classic instance of CWE-347: Improper Verification of Cryptographic Signature. The library failed to decouple the request for an algorithm from the policy enforcing it.
When a token arrives, the library parses the header to determine which algorithm was used to sign it. In the vulnerable versions, if the header claimed the algorithm was none, the library's verification logic short-circuited. It didn't just skip the signature check; it explicitly returned true (valid), assuming that if the token said it was unsigned, and the code reached that point, it must be acceptable. This effectively delegated the security decision to the attacker-controlled input.
Let's look at the crime scene. The vulnerability lived in JWS+Verify.swift. The logic was devastatingly simple. Here is the vulnerable verification function:
public func verify<Key>(key: Key?) throws -> Bool {
// VULNERABLE CODE
guard SigningAlgorithm.none != protectedHeader.algorithm else {
return true // <--- The fatal flaw
}
// ... actual cryptographic verification would happen here ...
}Do you see it? The guard statement checks if the algorithm is none. If it is none, the guard fails, executing the else block. The else block simply returns true.
This meant that if an attacker sent a token with {"alg": "none"}, the function would immediately return true without ever checking a key, a signature, or a whitelist. It essentially said: 'Oh, you aren't using encryption? Go right ahead.'
To fix this in version 6.0.2, the maintainers had to invert this philosophy. They introduced a check to ensure that even if none is somehow processed (which it shouldn't be in production), it cannot coexist with a signature, and it must be explicitly allowed by a validator.
Exploiting this requires no special tools—just a text editor and a basic understanding of Base64Url encoding. Here is how an attacker creates a Golden Ticket:
Capture a legitimate token: The attacker logs in as a low-privileged user to get a valid structure.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiYWRtaW4iOmZhbHNlfQ.signature_bytes
Modify the Header: Decode the first part. Change "alg": "HS256" to "alg": "none". Re-encode it.
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
Forge the Payload: Decode the second part. Change "admin": false to "admin": true (or inject any other claim you want). Re-encode it.
eyJzdWIiOiJ1c2VyMTIzIiwiYWRtaW4iOnRydWV9
Reassemble: Combine the new header and payload with a period. You can leave the signature empty or put garbage there, as the vulnerable code returns true before parsing the signature segment (though standard none implementation usually expects a trailing dot with no signature).
eyJhbGciOiJub25l... .eyJzdWIi... .
Attack: Send this token in the Authorization header. The verify() function sees alg: none, returns true, and the application processes the payload believing the user is an admin.
The remediation in version 6.0.2 (PR #62) introduces multiple layers of defense. First, it kills the short-circuit logic. Second, it adds sanity checks.
Here is the logic in the patched version:
public func verify<Key>(key: Key?) throws -> Bool {
// PATCHED LOGIC
if SigningAlgorithm.none == protectedHeader.algorithm, !signature.isEmpty {
// If alg is none, but you sent a signature, we know you are up to no good.
throw JWSError.algorithmNoneButSignatureFound
}
// Proceed to validator checks which now include a blacklist
// ...
}Furthermore, the developers introduced a SigningAlgorithmBlackListValidator. By default, this validator now runs on verification and explicitly blocks none unless the developer goes out of their way to remove the block. This shifts the library from 'insecure by default' to 'secure by default'.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
jose-swift beatt83 | <= 6.0.1 | 6.0.2 |
| Attribute | Detail |
|---|---|
| Attack Vector | Network (AV:N) |
| Attack Complexity | Low (AC:L) |
| Privileges Required | None (PR:N) |
| CWE | CWE-347 (Improper Signature Verification) |
| CVSS Score | 8.8 (Critical) |
| Exploit Status | Poc Available / Trivial |
The product verifies a cryptographic signature, but the verification logic is implemented incorrectly, allowing the signature check to be bypassed.