Description
The GET unsubscribe endpoint (/subscribe/unsubscribe) at subscribe.py:89-113 returns different error messages depending on whether the email exists in the database:
- If email not found:
{"message": "Subscription not found or already inactive."}
- If token doesn't match:
{"message": "Invalid unsubscribe link."}
An attacker can enumerate which email addresses are subscribed to the weekly digest by observing the error message. This is an information disclosure vulnerability.
Additionally, the unsubscribe flow uses GET requests with email and token as URL query parameters, which leak the unsubscribe token in server logs, browser history, and via referrer headers.
Affected Files
backend/app/routers/subscribe.py:89-113
Expected Behavior
- Both "not found" and "invalid token" cases should return the same generic message (e.g.,
"Unsubscribe link is invalid or expired.") to prevent enumeration.
- The unsubscribe action should use POST (not GET) to prevent token leakage via URL.
Labels
type:bug, level:intermediate, gssoc2026
Description
The GET unsubscribe endpoint (
/subscribe/unsubscribe) atsubscribe.py:89-113returns different error messages depending on whether the email exists in the database:{"message": "Subscription not found or already inactive."}{"message": "Invalid unsubscribe link."}An attacker can enumerate which email addresses are subscribed to the weekly digest by observing the error message. This is an information disclosure vulnerability.
Additionally, the unsubscribe flow uses GET requests with
emailandtokenas URL query parameters, which leak the unsubscribe token in server logs, browser history, and via referrer headers.Affected Files
backend/app/routers/subscribe.py:89-113Expected Behavior
"Unsubscribe link is invalid or expired.") to prevent enumeration.Labels
type:bug, level:intermediate, gssoc2026