Skip to content

OAuth refresh-token reuse not contained: no family revocation on detected reuse

Low
koala73 published GHSA-f6gj-3v7v-j75q Jul 4, 2026

Package

koala73/worldmonitor

Affected versions

<= 2.8.0 (main @ commit 16d0a12e)

Patched versions

None

Description

Summary

The OAuth authorization server rotates refresh tokens and preserves a family_id across rotations, and its doc-comment claims this "protects against refresh-token theft via family-revoke." But no code path ever reads family_id to revoke a family. When a stolen refresh token is redeemed, the legitimate client's later reuse of the now-stale token is detected (returns invalid_grant) yet triggers no revocation — the attacker's rotated token stays valid indefinitely. This is insufficient session/credential invalidation (CWE-613): a stolen refresh token cannot be contained.

Details

  • Store: api/oauth/token.ts:178-239 writes family_id alongside each token.
  • Rotate: api/oauth/token.ts:431-569 uses Redis GETDEL to atomically consume+rotate a single token and carries family_id forward unchanged.

The PoC classifies every family_id occurrence in token.ts: 5 writes + 9 type-declarations/comments, 0 revoke-reads. No index maps family_id → tokens, and no branch revokes a family on detected reuse.

When an attacker redeems a stolen refresh token, it rotates to a fresh token (attacker-held) and the stolen one is GETDEL-consumed. The legitimate client's subsequent attempt to use its (now stale) copy hits a GETDEL-miss and receives 400 invalid_grant — this is the classic reuse signal a spec-correct AS uses to revoke the entire token family. Here it is discarded: the attacker's rotated token keeps rotating with a fresh 7-day sliding TTL. Access is silent and indefinite.

PoC

poc-F4-oauth-refresh-family.mjs proves family_id is write-only (5 writes / 0 revoke-reads) and models the rotation timeline:

attacker redeems stolen RT     -> new RT_a issued, family_id preserved
victim redeems stale RT        -> 400 invalid_grant   (reuse DETECTED)
family revocation triggered?   -> NO  (no code reads family_id to revoke)
attacker RT_a still valid?     -> YES (rotates indefinitely, 7-day sliding TTL)

Impact

An attacker who has obtained a victim's refresh token (theft is a precondition, out of scope to achieve here) retains persistent, silent access that the reuse-detection signal should have terminated. The impact of this defect is the failure to contain an already-compromised session — not the initial compromise. Rated Low: it requires a pre-stolen high-value credential and adds persistence rather than new access.

Remediation

Maintain a family_id → tokens index (or an oauth:family:<F> generation counter). On a GETDEL-miss for a token belonging to a known family (the reuse signal), revoke the entire family so both the attacker's and the victim's tokens are invalidated, forcing re-authentication. A tested unified-diff patch (F4-refresh-token-family.patch) and an end-to-end revocation harness are available in the coordinated-disclosure package.


Maintainer validation (2026-07-04): Valid historically, but no longer live on current origin/main. OAuth refresh-token rotation now tracks family pointers/revocation, rejects reused/legacy refresh tokens by revoking the family, and has regression coverage in tests/oauth-token.test.mjs. Published advisories cannot be closed through the GitHub API, so this is marked in-description as fixed/no longer active.

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
High
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N

CVE ID

No known CVE

Weaknesses

Insufficient Session Expiration

According to WASC, Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization. Learn more on MITRE.

Credits