The dangerous flaws are rarely the loud ones. CVE-2026-53776 is a single inverted boolean inside a JWT helper, and it silently broke one of the few security controls every app assumes works: the ability to end a session. If you build on Perry, the native TypeScript compiler, and you lean on its bundled token helper, then your logout button has been decorative. Revoked tokens never died. Expired tokens never expired.
Here is the part that should worry you more than the bug itself: nothing looked broken. Valid tokens authenticated. Logins worked. Every functional test passed. The failure only shows up in the one test almost nobody writes, which is the negative one: hand the system a token that should be dead and confirm it gets rejected.
What CVE-2026-53776 actually is
Perry compiles TypeScript directly to native executables and ships its own standard library, including helpers for common tasks like verifying JSON Web Tokens. Inside that library, the verify_decode helper hard-codes validate_exp = false on every call. That one setting tells the verifier to skip the expiration check entirely. As long as the cryptographic signature is intact, the token is accepted, no matter how long ago it was supposed to die.
VulnCheck, the CNA that assigned the CVE, rates it 9.1 on CVSS 3.1 and 9.3 on CVSS 4.0, both critical. The weakness is classified as CWE-613, Insufficient Session Expiration. It affects every Perry release before 0.5.1166, which is the fix. The researcher credited on the VulnCheck advisory is Katriel Moses.
What makes the default so wrong is that it reverses the safe convention. Node's widely used jsonwebtoken library enforces the expiration claim by default; you have to opt out on purpose. Perry's helper did the opposite, opting everyone out with no flag to turn it back on. The project's own GitHub security advisory spells out the inversion.
Why a skipped expiry check is a critical bug
A bearer token is a key. Expiration is the part of the key that is supposed to crumble after a set time so a copied key stops working. Remove the expiry check and the key is permanent. Three things you thought you controlled stop working at once:
- Logout. The user clicks log out, the client drops the token, but the token itself is still valid to the server. Anyone holding a copy stays in.
- Forced session expiry. Short-lived tokens are a containment tool. If they never time out, a token stolen through a phishing page, a leaked log, or a shared device works indefinitely.
- Administrative revocation. The classic incident-response move, kill a compromised user's sessions, does nothing here unless you have a separate server-side denylist.
So far there is no public report of exploitation in the wild, and the bug's EPSS score sits low. That is the window, not the all-clear. This is the kind of flaw that gets quietly abused after the fact, because a stolen token from months ago suddenly turns out to still work.
The pattern: convenience defaults that disable a control
Strip away the specifics and this is a familiar shape. A batteries-included library picks a default that makes the happy path smoother and turns a security check off to get there. We have seen the same move as permissive CORS by default, TLS clients shipping with certificate verification disabled, and debug modes left on in production. The common thread is that the insecure choice is invisible, because the visible behavior is identical to the secure one right up until someone abuses it.
The lesson for anyone shipping software is not Perry-specific. Do not trust a framework or runtime to enforce a security control just because the documentation implies it. Verify the control fires. For authentication, that means an explicit test that an expired token, and a revoked one, are both rejected. The auth stack is exactly where teams assume the platform has their back and stop checking, which is the same blind spot we wrote about in the Velvet Ant PAM and OpenSSH case.
What to do now
If you ship anything built with Perry, treat this as today's work, in this order:
- Upgrade to Perry 0.5.1166 or later and rebuild every affected service. The fix restores expiration validation.
- Rotate your JWT signing keys. This is the only lever that actually invalidates every token already in circulation, including ones an attacker may already hold. Until you patch, key rotation is your emergency logout-for-everyone, and you should treat it as one.
- Add a denylist or short-lived refresh model if you do not have one. Server-side session state is the backstop for exactly this failure, where the token itself cannot be trusted to expire.
- Write the negative test and keep it. An automated check that feeds the verifier an expired token and asserts a rejection would have caught this on day one. It belongs in your suite permanently, not just for this CVE.
The single inverted boolean is fixed. The habit that let it ship, trusting a default to enforce a security control nobody tested, is the thing to fix in your own code this week.