On June 4, 2025, Asana identified a data-exposure bug in the MCP server it had launched on May 1, 2025. The server, which gave AI tools natural-language access to a customer's projects, tasks, and comments, allowed users querying through the MCP interface to retrieve data from other customers' Asana workspaces — limited to data types the requesting user already had permission to see, but not limited to data their own tenant owned. Asana took the server offline on June 5, fixed the underlying authorization logic, and brought it back online on June 17. The company notified roughly 1,000 affected customers and confirmed no evidence of malicious exploitation. As an AI security incident, the Asana case is unusually clean: there was no prompt injection, no jailbreak, no clever new attack class. The bug was a SaaS authorization break in a connector the world happened to call "MCP."
What was the actual technical defect?
The defect was incomplete access control enforcement in the MCP server's response assembly. When a user issued a tool call such as "list my tasks assigned this week," the server resolved the query against the LLM-friendly data model and returned results whose tenant scoping had been lost. Asana's customer-facing API enforces tenant isolation at the GraphQL layer; the MCP server ran a different code path that resolved entities by ID without re-checking the tenant binding. Because the AI client only knew about its own user identity, it accepted whatever the server returned, and the leaked rows surfaced inside the user's chat session as if they were the user's own data. The data-type limitation (a user could only ever retrieve task-shaped, project-shaped, or comment-shaped data) meant high-impact records like billing or admin-only configuration were out of scope, but task titles, comment bodies, and project descriptions across roughly 1,000 customers' workspaces became reachable.
Why did existing SaaS access control not catch this?
Because the MCP server was a new code path. Asana's GraphQL and REST APIs have years of authorization tests, tenant-scoped fixtures, and IDOR fuzzers pointed at them. The MCP server was a 30-day-old service that exposed a different surface — tools/call over JSON-RPC — and the team's threat model implicitly assumed the underlying data layer would enforce isolation. That assumption is a recurring failure pattern in connector launches: the connector is treated as a thin adapter, security tests are deferred until the adapter "stabilizes," and the same week it ships to GA, a researcher (or in this case, Asana's own staff) discovers a tenant boundary that does not hold. The Asana incident is structurally identical to the early-2020s pattern of SaaS vendors shipping mobile or partner APIs that bypassed the access controls of their web app.
What does the threat model for an MCP connector to a multi-tenant SaaS look like?
Three trust boundaries collapse into the connector. First, the user's tenant in the upstream SaaS — the boundary that is supposed to keep tenant A's data away from tenant B. Second, the user's authentication context inside the MCP client — the boundary that says "this person is logged in as this account." Third, the model's instruction context — the boundary between trusted user input and untrusted retrieved content. In a healthy design, all three are enforced independently. In Asana's bug, the first boundary collapsed inside the MCP server, and because the model treated whatever it received as ground truth, the collapse propagated all the way to the user's chat window. Defenders evaluating a multi-tenant MCP server should ask, for every tool the server exposes, which of the three boundaries that tool depends on and where each is enforced.
What controls would have prevented this?
Two cheap controls would have caught it. The first is a tenant-scoped fuzzer: a test harness that authenticates as user A in tenant 1, issues every tool call the server supports with object IDs from tenant 2, and asserts that every response is empty or returns a clean "not found." That harness is a few hundred lines of Python and would have flagged the Asana bug on day one. The second is an audit log that records, for every MCP response, the tenant IDs touched and compares them to the requesting user's tenant binding. The configuration below sketches the latter as a runtime guard rather than a test, useful when the SaaS team cannot rewrite the server but can wrap it.
# mcp-tenant-guard.yaml — runtime egress guard for SaaS MCP servers
guard:
name: tenant-binding-enforcer
applies_to: "mcp://asana.example.com/*"
pre_response:
- extract_field: "$.result.content[*].metadata.tenant_id"
collect_into: response_tenants
- extract_claim_from_token: "tenant_id"
collect_into: caller_tenant
- assert: "response_tenants is_subset_of [caller_tenant]"
on_fail:
action: block
log_to: siem
emit_signal: "mcp.cross_tenant.attempt"
audit_log:
sink: "splunk://mcp-audit"
fields: [user_id, caller_tenant, response_tenants, tool_name, request_id]
What changed in the Asana ecosystem after the incident?
Asana published a public post-mortem, the SANS NewsBites coverage on June 12 added it to the institutional memory of the SaaS security community, and CSO Online's piece for CISOs framed it as a leading indicator that every SaaS vendor's MCP launch deserves a separate authorization review. Internally, Asana's response was the textbook playbook: disable, fix, audit logs, customer notifications, transparent timeline. Externally, the incident triggered a wave of security teams asking their SaaS vendors a simple question — "do you have an MCP server, and if so, what is its tenant isolation model?" — which is a question worth standardising in TPRM questionnaires going forward. Pomerium, Nudge Security, and Quilr all published follow-up analyses framing the bug as a connector class issue, not an Asana-specific one.
What should a defender add to their TPRM and SaaS posture program?
Three additions. First, a question on the TPRM questionnaire that asks every vendor whether they expose an MCP server (or equivalent agentic connector), what version of the MCP spec it implements, and what the tenant isolation model is. Second, a SaaS configuration scan that detects when a vendor has rolled out an MCP server to your tenant and surfaces it for review before users can connect from Claude Desktop, ChatGPT, or Cursor. Third, an internal policy that any vendor's MCP connector — even from a vendor you trust — must be enrolled in a per-vendor agent risk register with the same fields you maintain for OAuth app integrations: scopes, data types reachable, last security review, owner of record. The Asana incident did not require a sophisticated attacker. It required only that the connector existed and that one customer's data could be returned to another customer's session.
How Safeguard Helps
Safeguard's third-party risk module tracks every SaaS vendor's MCP rollout, parses each connector's published metadata, and flags new MCP servers from vendors in your environment for security review before they reach end users. Griffin AI runs a tenant-scoped probe across enrolled MCP servers — issuing benign cross-tenant queries with synthetic IDs and confirming the server returns empty results — so a regression like the Asana bug surfaces as a finding rather than as a customer-reported incident. Policy gates block agentic clients from connecting to a SaaS MCP server until that server has passed the cross-tenant probe, and audit logs of every tool call ingest into the SIEM with caller-tenant and response-tenant fields side by side, making the cross-tenant signal in the snippet above a one-click query rather than a custom integration project.