
Library-level proof-of-concept lab demonstrating CVE-2026-29000 in pac4j-jwt, comparing vulnerable and patched versions with Docker to show forged JWT acceptance and rejection.
This repository contains a library-level PoC for CVE-2026-29000 in pac4j-jwt.
It compares vulnerable and patched behavior with two cases:
| Version | Baseline | Attack | Result |
|---|
6.0.3 | ✅ | ✅ | Vulnerable |
6.0.4.1 | ✅ | ✅ | Vulnerable |
6.3.3 | ✅ | ❌ | Patched |
This PoC demonstrates authenticated profile creation with attacker-controlled subject and roles on vulnerable versions, while the patched version rejects the forged token.
This is not a web application demo.
It is a small Java program that calls JwtAuthenticator directly and compares multiple versions of pac4j-jwt inside Docker.
The goal is to prove three things:
The tested versions were chosen deliberately:
This gives the lab three useful reference points:
.
├── docker-compose.yml
├── Dockerfile
├── pom.xml
└── src/main/java/lab/Repro.java
docker-compose.yml
Defines the test matrix for each version.
Dockerfile
Builds and runs the PoC inside a container.
pom.xml
Defines dependencies and builds a runnable fat JAR.
src/main/java/lab/Repro.java
The actual PoC harness.
The docker-compose.yml file defines three services:
v603 = test pac4j-jwt 6.0.3v6041 = test pac4j-jwt 6.0.4.1patched = test pac4j-jwt 6.3.3So these commands mean “run the PoC once against that specific version”:
docker compose run --rm v603
docker compose run --rm v6041
docker compose run --rm patched
--rm means the temporary container is removed after the run finishes.
docker compose build --no-cache
docker compose run --rm v603
docker compose run --rm v6041
docker compose run --rm patched
For each version, the program runs two cases.
It generates a legitimate token and validates it through JwtAuthenticator.
Expected result:
It generates a forged token with attacker-controlled claims and validates it through JwtAuthenticator.
Expected result:
You should see something like this:
[case] baseline
result: ACCEPTED
observed_subject: alice
observed_roles: [ROLE_USER]
[case] attack
result: ACCEPTED
observed_subject: admin#override
observed_roles: [ROLE_SUPERUSER, ROLE_ADMIN]
[summary]
conclusion: VULNERABLE: forged token accepted
Meaning:
You should see something like this:
[case] baseline
result: ACCEPTED
observed_subject: alice
observed_roles: [ROLE_USER]
[case] attack
result: REJECTED
reason: CredentialsException: A non-signed JWT cannot be accepted as signature configurations have been defined
[summary]
conclusion: PATCHED: forged token rejected
Meaning:
This CVE affects a library-level authentication path, not a single application with one universal role model.
The reusable part is the attack shape:
JwtAuthenticatorWhat is not universal across real applications:
Because of that, this repository focuses on proving that the library accepts forged attacker-controlled claims on vulnerable versions, rather than pretending there is one universal token that would automatically work against arbitrary applications.
docker compose run --rm v603docker compose run --rm v6041docker compose run --rm patched


This project demonstrates three core facts:
6.0.3 and 6.0.4.16.3.3This is the core vulnerable-versus-patched evidence for this CVE in this repository.
On vulnerable versions, the forged token is not merely parsed — it produces an authenticated profile with attacker-controlled subject and roles.