ChaCha20-Poly1305 is an authenticated cipher: ChaCha20 encrypts, Poly1305 produces a tag proving the ciphertext was not touched. Remove the tag and you keep a cipher that hides data and guarantees nothing about it.
What happened
The experimental Chacha20Poly1305 key-encryption algorithm generated its sixteen-byte Poly1305 tag correctly, and then discarded it. Nothing carried it, so nothing verified it on the way back.
The construction was silently degraded to unauthenticated ChaCha20. A tampered encrypted key was accepted without a murmur.
Why this class of bug is nasty
Everything works. Tokens encrypt, tokens decrypt, tests pass, round trips succeed. The missing property is one that only shows up when somebody attacks you, and the code carries the name of an authenticated cipher throughout.
A test that encrypts then decrypts will never catch it. The test that catches it flips one bit of the ciphertext and asserts that decryption fails. If it succeeds, the authentication is not there, whatever the class is called.
The fix, and a word on « experimental »
The tag is now emitted and verified. Fixed in 3.4.10, 4.0.7 and 4.1.7.
This algorithm lives in web-token/jwt-experimental, a package whose whole purpose is to hold what is not stable enough for production, and which says so. That warning is not decoration. If an experimental algorithm reached your production configuration, this is the reminder to go and look.