Security systems rely on secrets.
Private keys.
Signing keys.
Shared secrets between trusted systems.
Remove the secret, and authentication should collapse.
At least, that’s the theory.
During an internal research project at CodeAnt AI, we discovered a vulnerability in a widely used Java authentication library that broke this assumption entirely.
An attacker could authenticate as any user, including administrators, using nothing more than the server’s public key.
No private key.
No shared secret.
No brute force.
Just the public key that was designed to be public.

The issue has now been assigned CVE-2026-29000 with a CVSS score of 10.0 (Critical) and affects the pac4j-jwt authentication module.
This article explains how the vulnerability works, how it was discovered, and why it represents a broader class of authentication failures in modern software systems.
The Research Project That Found It
The vulnerability wasn’t discovered through a traditional bug bounty program.
It emerged from a research project focused on a different question entirely:
When open-source libraries patch security vulnerabilities, does the patch actually fix the problem?
Security advisories typically describe the issue and provide an updated version number. But they rarely explore whether the underlying code path has been fully addressed.
Our team began auditing patched vulnerabilities across major ecosystems: Maven, npm, PyPI, and NuGet looking specifically at:
patch diffs
execution paths around the patched code
assumptions made by the surrounding logic
Instead of manually reviewing each repository, we used our AI code reviewer to analyze code paths around patched CVEs.
During one of these scans, the system flagged a suspicious pattern inside the token validation logic of pac4j-jwt.
The anomaly looked small.
Just a null check.
But that null check sat directly in front of the code responsible for verifying JWT signatures.
When our security engineer reviewed the code path, the implication became clear immediately.
If that condition evaluated to false, signature verification would never run.
Yet authentication would still proceed.
That realization led to a deeper investigation.
How JWT Authentication Is Supposed to Work
Most modern authentication systems that rely on JWT tokens use two layers of protection.
Layer 1: Encryption (JWE)
The outer token is encrypted using JSON Web Encryption (JWE).
Encryption ensures the token’s contents cannot be read by intermediaries while traveling across the network.
In RSA-based deployments, the client encrypts the token using the server’s public key, and the server decrypts it using its private key.
Layer 2: Signature (JWS)
Inside the encrypted wrapper sits a signed JWT, known as JWS.
The signature proves that the token was generated by a trusted authority possessing the signing key.
This signature validation is what actually proves authenticity.
The intended authentication flow looks like this:
Each layer serves a different security purpose.
Encryption protects confidentiality.
Signatures protect authenticity and integrity.
Both must succeed before authentication should proceed.
But the vulnerability we discovered allowed the system to authenticate a user without ever verifying a signature.
The Line of Code That Made It Possible
The suspicious code appeared in the JwtAuthenticator implementation.
The authentication flow roughly followed these steps:
The key step was this line:
This method is part of the Nimbus JOSE+JWT library.
Its behavior is straightforward.
If the payload contains a signed JWT (JWS), the method returns the parsed object.
If the payload is not signed, it returns null.
This includes a token type called PlainJWT.
PlainJWT is a perfectly valid format defined in the JWT specification. It simply contains claims without a signature.
The pac4j authentication logic then included the following condition:
On the surface, this seems reasonable.
But the implications are subtle.
If signedJWT is null, meaning the payload is not signed, the signature verification block is skipped entirely.
The system then continues processing the token and eventually creates a user profile using the token’s claims.
Which means authentication can succeed without verifying authenticity at all.
Turning the Bug Into an Exploit
Once we understood the logic flaw, the next question was simple.
Could an attacker control the type of JWT inside the encrypted wrapper?
The answer turned out to be trivial.
Instead of generating a signed JWT, the attacker simply generates an unsigned PlainJWT.
Because the token is unsigned, the toSignedJWT() call returns null.
Which triggers the bypass.
The attack flow becomes surprisingly simple.
Step 1: Obtain the Public Key
In RSA-based JWT deployments, the public key is typically exposed through several mechanisms.
Common sources include:
JWKS endpoints (
/.well-known/jwks.json)application configuration files
TLS certificate metadata
public repositories
This is normal. Public keys are meant to be public.
Step 2: Craft Malicious Claims
Next, the attacker constructs whatever claims they want the server to accept.
For example:
The token now claims to represent an administrator.
Step 3: Create an Unsigned JWT
Instead of signing the token, the attacker generates a PlainJWT.
This token has no signature.
It is technically valid according to the JWT specification.
Step 4: Encrypt the Token
The attacker then encrypts the unsigned token using the server’s public key.
From the outside, the token appears indistinguishable from a legitimate encrypted JWT.
Step 5: Submit the Token
The attacker sends the token as a Bearer authentication header.
The server processes the request as follows:
At this point, the server has accepted arbitrary claims supplied by the attacker.
The attacker is now authenticated as whatever identity they placed inside the token.
Proof-of-Concept Results
To validate the vulnerability, our team built a working proof-of-concept exploit against a real pac4j-jwt configuration.
The exploit required only the server’s public RSA key.
When executed, the authentication output looked like this:
[BYPASS] Authenticated as: admin [BYPASS]
[BYPASS] Authenticated as: admin [BYPASS]
Authentication succeeded immediately.
No secrets were compromised.
The attack simply exploited an unexpected code path allowed by the JWT specification.
Why This Vulnerability Exists
This bug did not arise from broken cryptography.
It arose from a subtle assumption about how tokens would be structured.
The code assumed that if a token decrypted successfully, the inner payload would always be a signed JWT.
But the specification allows something else.
PlainJWT.
Each individual component behaved correctly:
the JWT specification allows unsigned tokens
the Nimbus library correctly returns null for non-signed tokens
the pac4j code correctly checks whether the value is null
Yet when combined, these behaviors created a scenario where authentication guarantees disappeared.
This type of vulnerability is often called a composition flaw.
Each component behaves correctly in isolation.
But the interaction between components produces a dangerous outcome.
Responsible Disclosure
After verifying the issue and building a proof-of-concept, we privately disclosed the vulnerability to pac4j maintainer Jérôme Leleu on February 28.
The report included:
a detailed technical explanation
exploit code
recommended remediation steps
The response was immediate.
The maintainer confirmed the vulnerability and began preparing patches across multiple version lines.
Within two days:
patched versions were released for pac4j 4.x, 5.x, and 6.x
a security advisory was published
our research team was credited for the discovery
This rapid response highlights the dedication of open-source maintainers who often manage critical infrastructure used by thousands of applications.
Are You Affected?
Applications may be vulnerable if they use:
pac4j-jwtRSA-based encrypted JWTs (JWE)
both encryption and signature configurations
JwtAuthenticatorfor authentication
Users should upgrade immediately to:
Organizations can quickly verify whether pac4j-jwt is present by checking dependency trees in Maven or Gradle.
The Bigger Security Lesson
Modern security failures rarely come from broken cryptography.
Instead, they arise from assumptions embedded in system design.
Assumptions about:
expected inputs
allowed token types
interactions between libraries
Attackers focus on paths that developers do not anticipate.
In this case, a single conditional statement allowed authentication to proceed without verifying a signature.
That assumption created a CVSS-10 vulnerability capable of granting full administrative access.
And it was discovered not through manual auditing alone, but through a combination of automated analysis and human review.
What Comes Next
This vulnerability is part of an ongoing research initiative at CodeAnt AI focused on auditing whether security patches in widely used open-source packages fully address the underlying issue.
As software supply chains continue to grow more complex, automated tools capable of reasoning about code paths and security assumptions will play an increasingly important role in identifying subtle vulnerabilities.
The pac4j finding is just one example.
Additional research from this project will be released as coordinated disclosure processes are completed.
Security systems rely on secrets.
Private keys.
Signing keys.
Shared secrets between trusted systems.
Remove the secret, and authentication should collapse.
At least, that’s the theory.
During an internal research project at CodeAnt AI, we discovered a vulnerability in a widely used Java authentication library that broke this assumption entirely.
An attacker could authenticate as any user, including administrators, using nothing more than the server’s public key.
No private key.
No shared secret.
No brute force.
Just the public key that was designed to be public.

The issue has now been assigned CVE-2026-29000 with a CVSS score of 10.0 (Critical) and affects the pac4j-jwt authentication module.
This article explains how the vulnerability works, how it was discovered, and why it represents a broader class of authentication failures in modern software systems.
The Research Project That Found It
The vulnerability wasn’t discovered through a traditional bug bounty program.
It emerged from a research project focused on a different question entirely:
When open-source libraries patch security vulnerabilities, does the patch actually fix the problem?
Security advisories typically describe the issue and provide an updated version number. But they rarely explore whether the underlying code path has been fully addressed.
Our team began auditing patched vulnerabilities across major ecosystems: Maven, npm, PyPI, and NuGet looking specifically at:
patch diffs
execution paths around the patched code
assumptions made by the surrounding logic
Instead of manually reviewing each repository, we used our AI code reviewer to analyze code paths around patched CVEs.
During one of these scans, the system flagged a suspicious pattern inside the token validation logic of pac4j-jwt.
The anomaly looked small.
Just a null check.
But that null check sat directly in front of the code responsible for verifying JWT signatures.
When our security engineer reviewed the code path, the implication became clear immediately.
If that condition evaluated to false, signature verification would never run.
Yet authentication would still proceed.
That realization led to a deeper investigation.
How JWT Authentication Is Supposed to Work
Most modern authentication systems that rely on JWT tokens use two layers of protection.
Layer 1: Encryption (JWE)
The outer token is encrypted using JSON Web Encryption (JWE).
Encryption ensures the token’s contents cannot be read by intermediaries while traveling across the network.
In RSA-based deployments, the client encrypts the token using the server’s public key, and the server decrypts it using its private key.
Layer 2: Signature (JWS)
Inside the encrypted wrapper sits a signed JWT, known as JWS.
The signature proves that the token was generated by a trusted authority possessing the signing key.
This signature validation is what actually proves authenticity.
The intended authentication flow looks like this:
Each layer serves a different security purpose.
Encryption protects confidentiality.
Signatures protect authenticity and integrity.
Both must succeed before authentication should proceed.
But the vulnerability we discovered allowed the system to authenticate a user without ever verifying a signature.
The Line of Code That Made It Possible
The suspicious code appeared in the JwtAuthenticator implementation.
The authentication flow roughly followed these steps:
The key step was this line:
This method is part of the Nimbus JOSE+JWT library.
Its behavior is straightforward.
If the payload contains a signed JWT (JWS), the method returns the parsed object.
If the payload is not signed, it returns null.
This includes a token type called PlainJWT.
PlainJWT is a perfectly valid format defined in the JWT specification. It simply contains claims without a signature.
The pac4j authentication logic then included the following condition:
On the surface, this seems reasonable.
But the implications are subtle.
If signedJWT is null, meaning the payload is not signed, the signature verification block is skipped entirely.
The system then continues processing the token and eventually creates a user profile using the token’s claims.
Which means authentication can succeed without verifying authenticity at all.
Turning the Bug Into an Exploit
Once we understood the logic flaw, the next question was simple.
Could an attacker control the type of JWT inside the encrypted wrapper?
The answer turned out to be trivial.
Instead of generating a signed JWT, the attacker simply generates an unsigned PlainJWT.
Because the token is unsigned, the toSignedJWT() call returns null.
Which triggers the bypass.
The attack flow becomes surprisingly simple.
Step 1: Obtain the Public Key
In RSA-based JWT deployments, the public key is typically exposed through several mechanisms.
Common sources include:
JWKS endpoints (
/.well-known/jwks.json)application configuration files
TLS certificate metadata
public repositories
This is normal. Public keys are meant to be public.
Step 2: Craft Malicious Claims
Next, the attacker constructs whatever claims they want the server to accept.
For example:
The token now claims to represent an administrator.
Step 3: Create an Unsigned JWT
Instead of signing the token, the attacker generates a PlainJWT.
This token has no signature.
It is technically valid according to the JWT specification.
Step 4: Encrypt the Token
The attacker then encrypts the unsigned token using the server’s public key.
From the outside, the token appears indistinguishable from a legitimate encrypted JWT.
Step 5: Submit the Token
The attacker sends the token as a Bearer authentication header.
The server processes the request as follows:
At this point, the server has accepted arbitrary claims supplied by the attacker.
The attacker is now authenticated as whatever identity they placed inside the token.
Proof-of-Concept Results
To validate the vulnerability, our team built a working proof-of-concept exploit against a real pac4j-jwt configuration.
The exploit required only the server’s public RSA key.
When executed, the authentication output looked like this:
[BYPASS] Authenticated as: admin [BYPASS]
Authentication succeeded immediately.
No secrets were compromised.
The attack simply exploited an unexpected code path allowed by the JWT specification.
Why This Vulnerability Exists
This bug did not arise from broken cryptography.
It arose from a subtle assumption about how tokens would be structured.
The code assumed that if a token decrypted successfully, the inner payload would always be a signed JWT.
But the specification allows something else.
PlainJWT.
Each individual component behaved correctly:
the JWT specification allows unsigned tokens
the Nimbus library correctly returns null for non-signed tokens
the pac4j code correctly checks whether the value is null
Yet when combined, these behaviors created a scenario where authentication guarantees disappeared.
This type of vulnerability is often called a composition flaw.
Each component behaves correctly in isolation.
But the interaction between components produces a dangerous outcome.
Responsible Disclosure
After verifying the issue and building a proof-of-concept, we privately disclosed the vulnerability to pac4j maintainer Jérôme Leleu on February 28.
The report included:
a detailed technical explanation
exploit code
recommended remediation steps
The response was immediate.
The maintainer confirmed the vulnerability and began preparing patches across multiple version lines.
Within two days:
patched versions were released for pac4j 4.x, 5.x, and 6.x
a security advisory was published
our research team was credited for the discovery
This rapid response highlights the dedication of open-source maintainers who often manage critical infrastructure used by thousands of applications.
Are You Affected?
Applications may be vulnerable if they use:
pac4j-jwtRSA-based encrypted JWTs (JWE)
both encryption and signature configurations
JwtAuthenticatorfor authentication
Users should upgrade immediately to:
Organizations can quickly verify whether pac4j-jwt is present by checking dependency trees in Maven or Gradle.
The Bigger Security Lesson
Modern security failures rarely come from broken cryptography.
Instead, they arise from assumptions embedded in system design.
Assumptions about:
expected inputs
allowed token types
interactions between libraries
Attackers focus on paths that developers do not anticipate.
In this case, a single conditional statement allowed authentication to proceed without verifying a signature.
That assumption created a CVSS-10 vulnerability capable of granting full administrative access.
And it was discovered not through manual auditing alone, but through a combination of automated analysis and human review.
What Comes Next
This vulnerability is part of an ongoing research initiative at CodeAnt AI focused on auditing whether security patches in widely used open-source packages fully address the underlying issue.
As software supply chains continue to grow more complex, automated tools capable of reasoning about code paths and security assumptions will play an increasingly important role in identifying subtle vulnerabilities.
The pac4j finding is just one example.
Additional research from this project will be released as coordinated disclosure processes are completed.
FAQs
What is CVE-2026-29000?
How does the pac4j JWT authentication bypass work?
Why is a public key enough to exploit this vulnerability?
What security lesson does the pac4j vulnerability highlight?
Which versions of pac4j are affected by CVE-2026-29000?
Table of Contents
Start Your 14-Day Free Trial
AI code reviews, security, and quality trusted by modern engineering teams. No credit card required!
Share blog:











