Trello PowerUp Integration — Accessing Other Users' Private External Data

DP
Deev Pal
Dec 10, 2024 12 min read
Redacted writeup. This vulnerability has been accepted and rewarded by Atlassian but remains unresolved. Specific endpoints, feature names, and reproduction steps have been intentionally withheld to prevent exploitation. This post will be updated with full technical details once the fix is deployed and disclosure is approved.

TL;DR

Trello supports PowerUps — integrations that connect third-party services to your board. One such PowerUp connects a popular external development platform, allowing users to link their external accounts and pull data (including private data) into Trello.

The PowerUp's authorization model is designed so that each user's connected external account is isolated — only you can see and interact with your own linked data. Other board members can't view, pull, attach, or remove your external data. They're prompted to connect their own account instead.

The problem: this isolation is only enforced on the client side (in the browser). At the API level, any board member can send requests to view, pull, attach, or remove any other user's connected external data — including private repositories, branches, commits, and files. The server doesn't verify that the requesting user owns the external data they're interacting with.

This vulnerability chains broken access control with private information disclosure. An attacker can expose another user's private external data to all board members — without the victim's knowledge or consent.

Background — PowerUps and Third-Party Integrations

Trello's PowerUp system lets you add third-party integrations to your boards. These integrations connect external services — code hosting platforms, design tools, cloud storage, CRM systems — to bring relevant data directly into your Trello workflow.

What's a PowerUp? Think of PowerUps as plugins or extensions for Trello. When you add a PowerUp to your board, it unlocks new features: buttons, menus, and data connections that weren't there before. For example, a code hosting PowerUp might let you link code repositories to Trello cards, so your team can see which code changes relate to which tasks.

When a user connects their external account through a PowerUp, they go through an OAuth authorization flow — they log into the external service and grant Trello permission to access their data. This is standard: you've probably seen "Authorize [App] to access your [Service] account?" prompts before.

The critical detail: when a user authorizes Trello to access their external account, Trello gains access to both public and private data. This includes private repositories, private branches, internal code, confidential file names — anything the user has access to on the external platform. That's why the authorization is per-user: only the person who authorized the connection should be able to interact with that data through Trello.

Why does Trello get access to private data? When you connect your account, the external platform asks something like: "This app wants to access your repositories, including private ones. Allow?" Most users click "Allow" because they want to link their private project repos to their Trello cards. The assumption is that only they will interact with this data through Trello. The vulnerability breaks this assumption.

The Permission Model

The intended permission model for this PowerUp is straightforward and sensible:

  • Each user's connected account is isolated. When User A connects their external account, only User A can browse, pull, attach, and remove their data through the PowerUp.
  • Other users are prompted to connect their own accounts. When User B opens the PowerUp settings, they don't see User A's data. Instead, they see a prompt to connect their own external account.
  • No cross-user access. User B should have zero ability to interact with User A's connected data — not to view it, not to pull it into the board, not to attach it to cards, and not to remove it.

In the UI, this works correctly. When another user opens the PowerUp, they see a connect/authorize screen — no indication that another user has already linked their account, and no way to access that user's data through the interface.

An analogy: Imagine a shared office (the Trello board) where each employee has a locked filing cabinet (their connected external account). The cabinet can only be opened with the owner's key. Other employees can see that a filing cabinet exists, but they can't open it or take anything out. They need to bring their own filing cabinet if they want to store documents. This vulnerability is like discovering that while the cabinet looks locked, any employee can reach in from the back and take whatever they want.

Where It Breaks — Client-Side vs Server-Side

The isolation described above — "only the owner can interact with their data" — is enforced only on the client side. The browser JavaScript hides the other user's data and shows a "connect your own account" prompt instead. But the underlying API endpoints accept requests from any board member, regardless of who connected the external account.

What does "client-side only" mean? There are two places a web application can enforce security rules: in the browser (client-side) or on the server (server-side). Client-side enforcement is like a bouncer who checks IDs at the door but doesn't lock the back entrance. If someone knows the back entrance exists (i.e., they can send API requests directly using a tool like Burp Suite), they bypass the bouncer entirely. Server-side enforcement is like having locks on every door — no matter how you try to get in, the building checks your authorization. This PowerUp only had the bouncer, not the locks.

This means that by crafting API requests directly (bypassing the UI), any board member can:

  1. View another user's connected external data
  2. Pull that data into the board
  3. Attach that data to cards visible to all board members
  4. Remove another user's connected data from the board

All of these actions use the attacker's own session cookies and CSRF token — no victim credentials needed. The attacker just references the victim's data through identifiers that are accessible to any board member.

Three Attack Vectors

The vulnerability manifests in three distinct attack scenarios, each escalating in impact. Since the vulnerability is unresolved, the specific API endpoints and request payloads are redacted.

Attack 1: Pulling Private External Data to the Board

The first attack allows a board member to pull another user's connected external data into the shared board — including data from private repositories that the attacker would normally have zero access to.

HTTP Request — Redacted
PUT /1/board/[BOARD_ID]/██████████/██████████████████████ HTTP/2
Host: trello.com
Cookie: <ATTACKER-COOKIES>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

value=[REDACTED — contains victim's external account data]
&dsc=<ATTACKER-CSRF-TOKEN>

Response: 200 OK

The attacker uses their own cookies and CSRF token — not the victim's. The only "victim" data in the request is an identifier for the victim's connected account, which is accessible to any board member through a standard API call.

What happens here: Imagine a shared team dashboard (the board). User A has connected their private code repository to the dashboard — they can see their repos listed, but nobody else can. The attacker sends a single API request that says "pull User A's private repo X onto the dashboard." The server processes this without checking whether the attacker has the right to access User A's repo. Now the private repo data appears on the shared dashboard where everyone can see it.
After this request, the victim's private external data is visible on the shared board. Any board member can now see data that was supposed to be accessible only through the victim's authorized connection.

Attack 2: Attaching Private Data to Public Cards

The second attack goes further: the attacker can attach specific external data (branches, commits, files) from the victim's private account directly to Trello cards. These attachments become visible to every board member.

HTTP Request — Redacted
POST /1/cards/[CARD_ID]/attachments HTTP/2
Host: trello.com
Cookie: <ATTACKER-COOKIES>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

url=[REDACTED — URL to victim's private external resource]
&name=[REDACTED]
&dsc=<ATTACKER-CSRF-TOKEN>

Response: 200 OK

This turns a broken access control issue into a private information disclosure vulnerability. The attacker can systematically attach data from the victim's private repositories — branch names, commit messages, file names, issue titles — to cards that are visible to all board members.

The escalation: Attack 1 pulls private data onto the board (visible in the PowerUp settings). Attack 2 attaches it to actual Trello cards — making it permanently visible, searchable, and exportable. Even if the PowerUp is later disconnected, the card attachments remain. The victim's private data is now embedded in the board's history.

Attack 3: Removing the Victim's Connected Data

The third attack is destructive: the attacker can remove the victim's connected external data from the board entirely. After this, the victim's integration is broken — their linked data disappears, and they may need to re-authorize and reconfigure everything.

HTTP Request — Redacted
PUT /1/board/[BOARD_ID]/██████████/██████████████████████ HTTP/2
Host: trello.com
Cookie: <ATTACKER-COOKIES>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

value=[REDACTED — empty payload that clears the victim's data]
&dsc=<ATTACKER-CSRF-TOKEN>

Response: 200 OK

This attack uses the same endpoint as Attack 1, but with a payload that clears the victim's data instead of pulling it. The server processes the destructive request without verifying that the attacker has authority over the victim's connected account.

Combined impact: An attacker could execute all three attacks in sequence — first pull the victim's private repos (to browse what's available), then attach the most sensitive items to public cards (information disclosure), then remove the victim's connection entirely (covering their tracks and causing disruption). The victim may not even realize what happened until they notice their integration is broken.

Root Cause — Why This Happens

The root cause is a pattern we've seen across multiple Trello vulnerabilities: client-side authorization that isn't replicated on the server.

The PowerUp's frontend (JavaScript in the browser) correctly implements the isolation model. When User B loads the PowerUp settings, the UI hides User A's data and prompts User B to connect their own account. From a UI perspective, User B has no way to interact with User A's data.

But the API endpoints behind the PowerUp do not perform the same ownership check. They verify that the requester is a board member (a very broad check) but don't verify that the requester is the owner of the connected external account (the check that actually matters).

Pseudocode — Authorization Gap
// What the UI does (client-side)
function showPowerUpSettings(user, board) {
    const connectedAccount = getConnectedAccount(user.id, board.id);

    if (!connectedAccount) {
        showAuthorizationPrompt();  // "Connect your account"
        hideOtherUsersData();       // User can't see anyone else's data
        return;                     // ← UI-level isolation works ✓
    }

    showUserData(connectedAccount); // Only show THIS user's data
}

// What the API does (server-side)
function handlePluginDataUpdate(request) {
    const user = authenticate(request.cookies);
    if (!user) return 401;

    const board = getBoard(request.boardId);
    if (!board.hasMember(user.id)) return 403;

    // ✗ Missing: is this user the OWNER of this plugin data?
    // ✗ Missing: does this user have the right to modify THIS connected account?

    updatePluginData(request.pluginDataId, request.value);  // Accepts blindly
    return 200;
}

The server checks: "Is this person a member of the board?" — yes. It does not check: "Is this person the owner of the specific external account they're trying to interact with?" That second check is the one that matters, and it's completely absent.

The pattern: This is the same class of vulnerability as the Butler board button bypass I wrote about earlier — the UI restricts an action, but the API doesn't enforce the same restriction. In the Butler case, the button was hidden from non-owners in the UI but executable via the API. Here, the external data is hidden from non-owners in the UI but accessible via the API. Same root cause, different feature.

Impact

CVSS 3.0: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N — 8.1 (High)

  • Confidentiality: High — private external data (repository names, branch names, commit messages, file names, issue titles, private user information) can be disclosed to unauthorized board members
  • Integrity: High — the attacker can modify the victim's PowerUp configuration (pulling and removing external data) without authorization
  • Availability: Low — the attacker can disrupt the victim's integration by removing their connected data

What specific private data can be exposed:

  • Private repository names — revealing what projects a user is working on
  • Branch names — often containing ticket numbers, feature names, or internal project codenames
  • Commit messages — potentially containing sensitive context about changes
  • Private file and directory names — revealing codebase structure
  • Issue titles — potentially containing vulnerability reports, security issues, or internal discussions
  • Private collaborator names — revealing who has access to private projects
Real-world scenario: A CTO connects their account to the team's Trello board. Their connected account has access to private repositories containing proprietary algorithms, security configurations, infrastructure-as-code, and secret management tools. A junior developer — or worse, a recently offboarded contractor who still has board access — can use this vulnerability to browse, enumerate, and expose all of that private data to every board member. The CTO never consented to sharing this data, and the attacker never needed to compromise the CTO's credentials.

A Note on Bounty Scope

An interesting side story: the initial bounty payout was $600 instead of $1,200. The triage team classified this as a "first-party Trello app vulnerability" and applied a lower reward table.

I pushed back on this. The vulnerability is in Trello's API — every request in the report has Host: trello.com in the header. The external platform's servers are never contacted during the attack. Trello's endpoint accepts and processes the requests; Trello's server fails to check authorization. The bug is in Trello's code, not the external platform's.

After review, Atlassian agreed and updated the reward to $1,200, matching the standard P3 payout for the Trello target.

Lesson for researchers: If you believe your bounty was calculated against the wrong scope or reward table, push back professionally with evidence. In this case, pointing to the Host header in every request was sufficient to demonstrate that the vulnerability was in Trello's API, not in a third-party integration. Programs sometimes misclassify scope during triage — a polite, evidence-based response usually resolves it.

Timeline

  • 2024-12-10 — Vulnerability discovered and reported via Bugcrowd
  • 2024-12-12 — Triaged as P3 (Broken Access Control > Privilege Escalation)
  • 2024-12-12 — Initial bounty: $600 (classified as first-party app)
  • 2024-12-13 — Scope challenge submitted — argued the bug is in Trello's API (Host: trello.com)
  • 2024-12-19 — Bounty updated to $1,200 (reclassified as Trello target)
  • Present — Unresolved — fix has not yet been deployed

Takeaways for Hunters

  1. PowerUps and integrations are high-value targets. Trello's PowerUp system is a massive attack surface — dozens of third-party integrations, each with their own data access patterns, each with their own authorization logic (or lack thereof). When you see a PowerUp that connects to an external service, ask: "Does the API enforce the same per-user isolation that the UI shows?" If the UI prompts you to connect your own account but the API lets you interact with someone else's, you have a bug.
  2. Client-side restrictions are not security. This is the third Trello vulnerability in this blog series where the UI correctly restricts an action but the API doesn't enforce it. If a feature hides data from you in the browser, always test whether you can access it by sending the request directly. The UI is a convenience layer, not a security boundary.
  3. Look for per-user data in shared contexts. Whenever a shared resource (a board) contains per-user data (connected external accounts), there's a potential isolation boundary to test. Can User A's data be accessed by User B? The more sensitive the per-user data (private repositories, credentials, personal information), the higher the impact if the boundary is broken.
  4. Three-dimensional access control testing. Don't just test "can I read other users' data?" Test the full CRUD spectrum: Can you Create (pull/add) data on behalf of another user? Can you Read (view) their data? Can you Update (modify/attach) their data? Can you Delete (remove) their data? Each operation might have different authorization behavior.
  5. Challenge scope classifications. If a bug bounty program misclassifies your finding's scope, provide evidence and push back. Check which server handles the vulnerable requests (the Host header), which codebase contains the authorization gap, and which reward table should apply. A polite, factual response usually gets it resolved.
Full writeup coming soon. Once Atlassian resolves this vulnerability and disclosure is approved, this post will be updated with the complete technical details — specific endpoints, full request/response pairs, and step-by-step screenshots from the original report. Follow @techycodec08 on X for the update.