Skip to content

fix(api): require exp claim when verifying access tokens - #779

Merged
lukasIO merged 5 commits into
livekit:mainfrom
SashaMIT:fix/require-exp-on-token-verify
Aug 19, 2026
Merged

fix(api): require exp claim when verifying access tokens#779
lukasIO merged 5 commits into
livekit:mainfrom
SashaMIT:fix/require-exp-on-token-verify

Conversation

@SashaMIT

Copy link
Copy Markdown
Contributor

Problem

TokenVerifier.verify decodes with issuer, HS256, and leeway, but does not require exp. PyJWT treats a missing exp as optional, so a hand-rolled token with a valid signature and no expiry verifies forever.

First-party AccessToken.to_jwt always sets exp. This matches the gap livekit/protocol#1706 closed on the Go verifier (jwt.WithExpirationRequired).

Fix

When verify_signature is true (the production path), pass require: ["exp"]. verify_signature=False (simulate/dev) is unchanged.

Threat-model: the attacker can mint or obtain an HS256 token that omits exp. They do not control the verifier secret or clock. Without the claim, signature verification still succeeds and the credential never ages out. That is permanent access from a forgotten expiry, not host or agent authority.

Test plan

  • pytest tests/api/test_access_token.py (5/5)
  • Revert-tested: removing require: ["exp"] makes test_verify_token_missing_exp fail (missing-exp token is accepted)

Made with Cursor

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

A shared options dict from a ternary widens to dict[str, object]
and fails livekit-api type-check against PyJWT Options.

@lukasIO lukasIO left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for catching this!

Comment thread livekit-api/livekit/api/access_token.py Outdated
Comment on lines +238 to +239
# Inline each options literal so mypy matches PyJWT's Options TypedDict
# (a shared dict from a ternary widens to dict[str, object]).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Inline each options literal so mypy matches PyJWT's Options TypedDict
# (a shared dict from a ternary widens to dict[str, object]).

we can remove this section of the comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Thanks for the review.

@SashaMIT

Copy link
Copy Markdown
Contributor Author

Dropped the mypy note. Thanks for the review.

@lukasIO
lukasIO merged commit 4201bd5 into livekit:main Aug 19, 2026
9 checks passed
@SashaMIT

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants