Admin Privilege Escalation via Desktop Client: Bypassing Subscription Gating
A business logic flaw let an organization owner invite a teammate directly as an admin through the desktop client, completely skipping the pro-subscription check enforced on the web app.
Admin Privilege Escalation via Desktop Client: Bypassing Subscription Gating
TL;DR
A SaaS product I tested (API-tooling platform, name withheld) gated admin role assignment behind a paid "Pro" subscription. The web app enforced this correctly — you couldn't invite or promote a user to admin without upgrading. The desktop client, however, let you invite a new collaborator directly as an admin, with no subscription check at all. The result: full admin privileges for free, and a broken revenue control.
Background
The platform in question offers organization-level collaboration, with roles typically split into member and admin tiers. Admin access is meant to be a paid feature — part of what justifies the Pro subscription price point. That means the authorization logic for "can this org assign an admin role" is really a billing control as much as a security control, and it needs to hold no matter which client is asking.
The Vulnerability
The core issue was a classic inconsistent enforcement across clients: the same backend action (assign admin role to a new or existing member) was validated differently depending on whether the request originated from the web app or the desktop app.
- Web app: When inviting a user, the admin role option was either grayed out or explicitly required a Pro subscription. The client correctly reflected — and the server correctly enforced — the paywall.
- Desktop app: The invite flow exposed an admin option that worked regardless of the organization's subscription tier. Inviting a user directly as admin succeeded, the invitee accepted, and they landed with full admin capabilities — while the org's billing status stayed on the free tier the entire time.
This strongly suggests the authorization check was implemented client-side or in a code path specific to the web UI, rather than as a server-side invariant applied uniformly to every request that could grant an admin role — regardless of origin (web, desktop, or API).
Steps to Reproduce
- Set up two accounts: an "attacker" org owner account and a "victim" test account.
- On the web app, attempt to invite the test account as admin — confirm the option is blocked or requires an upgrade.
- On the desktop app, log in with the same org owner account and invite the test account directly with the admin role selected.
- Have the test account accept the invite.
- Log in as the test account and confirm full admin functionality is available.
- Confirm the organization's subscription/billing status is still free-tier throughout.
Impact
Business impact
- Organizations can obtain a paid feature (admin seats) for free, directly undermining the subscription model.
- Any customer aware of this could route all privilege elevation through the desktop client and simply never pay for Pro.
Security impact
- Unauthorized privilege escalation: a role that should require payment (and, implicitly, additional trust/vetting) can be granted freely.
- Admins typically get broader access to organization settings, member management, and potentially sensitive shared data — so this isn't just a billing bypass, it's an access-control bypass with real blast radius.
Root cause
Authorization for a sensitive, monetized action (admin role assignment) was not enforced as a single, consistent server-side check applied to every client. The desktop client's request path skipped a validation step the web client's path included.
Why This Class of Bug Is Common
Multi-client products (web, desktop, mobile, public API) tend to accrete separate code paths for the same logical action over time. It's easy for a subscription or permission check to get implemented once — in whichever client shipped the feature first — and never get pushed down into a shared, server-side enforcement layer. The desktop app in this case likely called a slightly different endpoint, or the same endpoint without a client-supplied flag the server (incorrectly) trusted, or simply predates the paywall logic that was later bolted onto the web flow.
The general lesson: any check that matters for security or billing has to live on the server, and it has to apply uniformly to every caller — not just the client you tested first.
Recommended Fixes
- Enforce subscription-tier checks for role assignment server-side, at the point where the role change is persisted — not in any individual client.
- Apply the same validation logic to every entry point that can create or modify a collaborator's role: web, desktop, mobile, and public/internal APIs.
- Treat subscription state as authoritative data fetched and checked at request time, not something any client is trusted to enforce or report.
- Add logging/alerting on privilege elevation events, especially ones that succeed despite a non-Pro subscription, to catch regressions like this early.
Timeline
- Report submitted: November 3, 2025
- Disclosed here in anonymized form after the standard bug bounty disclosure process. Duplicate to high severty report.
Bug bounty report status showing duplicate to high severity finding
This writeup is based on a report I submitted through a public bug bounty program. Company-identifying details have been removed/anonymized. The underlying pattern — inconsistent server-side enforcement across web and desktop clients — is worth checking for in any multi-client product with paywalled roles or features.