Session lifetime is a security control, not a UX setting
A Zoom zero-click RCE went from discovery to working exploit in a day. The exploit took one day; your sessions last longer than that.
On June 8, 2026, a researcher at A Security found a memory-corruption bug in Zoom's annotation feature. On June 9 he had a confirmed zero-click remote code execution against Zoom client v7.0.5, working on Windows, macOS, iOS, and Android. The whole run, from finding the flaw to building a working exploit, took fewer than 20 prompts on publicly available AI models, in under 24 hours. Cofounder Omer Gull put the delta to WIRED plainly: "Before it would have taken a team of five people maybe six months with a lot of refining and iteration to find this. Now people can reach the same results with under 20 prompts."
Patch speed is the number everyone reached for after the disclosure, and it's your vendor's clock, not yours. Here's a number that is yours, sitting in a configuration file you own, almost certainly set by someone who wasn't thinking about security when they set it: how long a stolen session stays valid.
If you run Zoom, the practical part is short. The bug chain is CVE-2026-53413, CVE-2026-53414, and CVE-2026-53415, all in the annotation protocol, all fixed in Workplace 7.1.5 and 7.0.6. Zoom also shipped a server-side filter that drops malicious annotation messages before they reach clients, and that filter carries a gap which inverts the advice you'd expect: it cannot apply to end-to-end encrypted meetings, where the server is deliberately blind to call content. So until every client is updated, A Security's guidance is to turn E2EE off. It keeps the filter in place, and E2EE wouldn't have stopped this attack anyway, since the attacker sits in the meeting holding the keys.
The rest of this post is about the number you own.
Session length is not a comfort setting
Ask why your session lifetime is what it is and the answer is usually about friction. Users complained about getting logged out. Support tickets went down when someone raised it. Or nobody set it at all and it's whatever the default was. The number lives in the same mental category as toast duration and pagination size, which is to say it gets tuned by how often people complain and never revisited.
Now read the same number the way an attacker does. Session lifetime is how long a credential keeps working after it's been copied. It's not a preference. It's the expiry date on your worst day.
Put arithmetic on it. AuthKit's default refresh token lifetime is 7 days. A refresh token copied off a laptop on Monday morning is still minting fresh access tokens the following Monday: after your EDR removed the malware, after the ticket closed, usually after the incident review. Cut that to 24 hours with an inactivity timeout underneath it, and the same theft, with the same malware and the same response, is dead before anyone writes the postmortem. Nothing about the attack changed. The only thing that changed was a number in your auth configuration.
That's the whole argument. Everything below is what follows from it.
What survives the eviction
Be precise about what session controls do and don't buy you, because the honest version is narrower than the pitch and still worth the work.
While an attacker is running code as the user, session lifetime is not the constraint. They hold a live session. They can wait for your app to refresh and take the new token, capture a re-authentication challenge as the user completes it, or skip tokens entirely and drive the browser. Nothing in your auth configuration fixes that. Endpoint detection is what evicts them, which is why A Security's guidance to alert on a conferencing client spawning a browser or a shell is the right first control. Their exploit made zoom.us open Safari.
What session controls decide is what's left afterward. The implant gets cleaned up in an afternoon. The credentials it copied on the way out keep working until something explicitly kills them: cookies from a browser profile, a refresh token on disk, a personal access token in a dotfile, an OAuth grant held by a background job. The goal is that no credential outlives the malware that stole it, and right now, for most teams, credentials outlive it by about a week.
Zoom is the current example, not the category. Anything that runs code as the user reaches the same drawer: an infostealer in a malicious npm package, a laptop lifted from a hotel room, a phished session cookie, a compromised browser extension. The delivery method is what makes the news. The loot is always the same, and so is the containment question.
This is also the same failure the SCIM offboarding gap exposes from the other direction. Flipping a user's active attribute to false changes a database field. It does not reach into sessions and tokens already issued, and any cookie or refresh token minted before that moment keeps working until your app revokes it. API keys are worse, because they usually aren't tied to the identity record at all. An attacker holding stolen tokens and a terminated employee holding stale ones are the same engineering problem, which is the good news, because closing the gap once shortens both.
Four settings that set the blast radius
None of these are exotic. All of them are configuration you can change this quarter, and all of them decide how much of an endpoint compromise is still live next week.
Token lifetime
AuthKit's defaults are 7 days for refresh tokens and 5 minutes for access tokens. The access token number is the one people notice; the refresh token number is the one that matters after a compromise, because it sets how long a copied credential can keep minting new access once the attacker is off the machine. AuthKit also supports a session inactivity timeout alongside maximum session length and access token duration, which is the cheapest way to shrink the tail without punishing people who are actually working.
Rotation
Refresh token rotation invalidates the old refresh token every time it's exchanged and issues a new one, so a replayed token fails. The stolen copy stops working the moment the real session refreshes. Turning that failure into an alert is your job, not the token's, and it's one of the few signals that tells you a copy existed at all. For browser clients, keeping the refresh token in an HttpOnly cookie puts it out of reach of client-side code, which closes the XSS path and does nothing at all about code already running as the user.
Revocation you can run under pressure
The WorkOS Sessions API lists all active sessions for a user and revokes them:
POST /user_management/sessions/revoke takes a single session_id, so the list you iterate over is what defines the scope of the operation. Which means the snippet above has a trap in it. listSessions returns a paginated list that defaults to 10 records, so on a user signed in from more devices than that, the loop reports success and leaves the rest live. Pass a higher limit and follow the after cursor until it comes back empty. Write that part before you need it, not during.
Every revocation emits a session.revoked event you can consume over the Events API or webhooks, which is how a user's other devices learn to drop local state instead of discovering it on their next failed request. The org-wide version wraps the same loop in a loop over every member, which is where rate limits start to matter too. And note the boundary: this kills sessions. The personal access token in a dotfile and the background job's OAuth grant are a separate inventory you still have to sever yourself.
Freshness on the actions that matter
A session that authenticated eight hours ago and a session that just proved who it belongs to are not equivalent, even though most apps treat them identically. AuthKit's step-up authentication puts a number on it: an auth_time claim on access and ID tokens carrying the timestamp of the last active authentication, which refresh tokens do not bump.
The check is the security boundary, and it belongs on the server:
When it comes back stale, send the user through AuthKit with max_age set. That parameter, not the claim, is what's actually enforced:
max_age is enforced at the authorization endpoint, and max_age=0 forces re-authentication every time. Outside Next.js the same parameter goes on workos.userManagement.getAuthorizationUrl({ clientId, provider: 'authkit', redirectUri, maxAge }). Check your versions before you write either one: maxAge needs @workos-inc/authkit-nextjs 4.2.0 or later and @workos-inc/node 10.7.0 or later, and on older releases it's typed as never, so TypeScript will tell you before runtime does.
The objection worth answering
The prevention objection is handled above: none of this stops the attack, and it isn't meant to. These are containment controls, and they decide how much of the compromise is still usable after your EDR does its job.
The one that actually kills these projects is operational. Short sessions are hostile. Every re-auth prompt is a support ticket, and teams that tighten token lifetimes across the board tend to loosen them again within a quarter, which is exactly how the number ended up being a UX setting in the first place.
The way out is to stop treating session length as one global dial. Step-up authentication exists so a sensitive action can require fresh proof without ending the session. On completion, AuthKit issues new tokens with an updated auth_time, rotates the refresh token, leaves the session ID unchanged, and fires an authentication.reauthenticated event. The user stays logged in. The dangerous operation gets a checkpoint. Long sessions for reading, minutes-old proof for deleting, and the difference is a threshold you pick per route rather than a compromise you make once for the entire product.
Rehearsed, or you don't have it
Having the API is not the same as having the capability. The question to answer before the next disclosure: how long does it take, right now, to invalidate every session for one compromised user, and for every user in one organization?
If the answer involves finding the right runbook, paging the person who wrote the revocation script, or a database migration, then your effective response time is that number, not the API's. Run it as a drill on a real account this month and time it. If it takes longer than a few minutes, you don't have containment, you have a plan to eventually have containment. Check that other devices actually drop, that background jobs holding OAuth grants get caught, and that API keys the user created are in scope, since they usually live outside the identity record entirely.
A Security's exploit finished by making zoom.us launch Safari on the victim's Mac, a deliberately harmless payload standing in for anything else the process could do. The next team to pull this off in a day won't pick a harmless one, and you won't get to decide whether they land. You do get to decide how long they keep what they take.