GHSA-3r68-hf9h-887vHighCVSS 8.8

MCP Atlassian: ENABLED_TOOLS / Toolset authorization bypass

Published
September 22, 2026
Last Modified
September 22, 2026

🔗 CVE IDs covered (1)

📋 Description

Summary

ENABLED_TOOLS and TOOLSETS filters are enforced at tools/list time only. tools/call dispatches from the full unfiltered tool registry (73 tools). Any user with access to the server endpoint that knows a tool name can invoke it directly. Tool names are not secret since mcp-atlassian is open source. Any direct JSON-RPC call bypasses the restriction entirely.

READ_ONLY_MODE is not affected: it has dual enforcement at list time (_list_tools_mcp) and call time @check_write_access decorator. The developers applied the correct pattern to READ_ONLY_MODE but not to ENABLED_TOOLS or toolsets - confirming this is an implementation oversight.

Impact

Any user with access to the MCP server's HTTP endpoint can invoke any of the 73 registered tools regardless of ENABLED_TOOLS or TOOLSETS configuration - including write and delete operations on Jira issues, Confluence pages, etc. Operators deploying mcp-atlassian via Streamable HTTP rely on ENABLED_TOOLS to enforce least-privilege access; the bypass invalidates that model entirely.

The security impact concentrates in multi-user / HTTP-transport deployments, where the tool filter is a trust boundary between clients. In a single-user stdio deployment there is no second principal to defend against.

Details

In src/mcp_atlassian/servers/main.py, AtlassianMCP overrides _list_tools_mcp and applies the TOOLSETS and ENABLED_TOOLS filters before returning the tool list to clients. The _call_tool_mcp handler is not overridden. FastMCP's default _call_tool_mcp resolves the tool from the local tool manager / mounted servers, the full unfiltered inventory, so the filters never apply at call time.

Proof of Concept

All calls are issued against the HTTP transport, with ENABLED_TOOLS=jira_search configured - only jira_search should be reachable.

Step 1 - negative control: tools/list correctly filters by ENABLED_TOOLS:

req → tools/list
← { "tools": [ { "name": "jira_search" } ] }   # only 1 tool; jira_get_issue / jira_create_issue absent

Step 2 - confidentiality bypass: tools/call dispatches jira_get_issue despite its exclusion from the list:

req → tools/call jira_get_issue { "issue_key": "SEC-1" }
← { ... issue fields (summary, status, ...) ... }   # executed; not blocked

Step 3 - integrity bypass: tools/call dispatches the write tool jira_create_issue:

req → tools/call jira_create_issue
         { "project_key": "SEC", "summary": "[PoC] ENABLED_TOOLS bypass", "issue_type": "Task" }
← { ... "key": "SEC-<n>" ... }   # issue created despite ENABLED_TOOLS=jira_search

Step 1 proves the filter exists and is enforced at list time, so Steps 2–3 are a genuine authorization bypass, not an open/unconfigured endpoint. The same result holds with TOOLSETS=jira_read configured: write tools are absent from tools/list yet execute via tools/call.

Local reproduction

Extract enabled_tools_bypass_tool_authorization.zip:

# Fill Atlassian credentials in docker-compose.yml (ENABLED_TOOLS=jira_search is preset);
# ensure issue SEC-1 exists, or update the project/issue key in poc.sh
docker compose up -d        # mcp-atlassian, streamable-http, 0.0.0.0, ENABLED_TOOLS=jira_search
./poc.sh                    # exits 0 on success
docker compose down -v

Requires Docker, curl, jq, and an Atlassian Cloud site with a Jira project (free tier works). The script runs the three steps above: it confirms tools/list returns only jira_search, then dispatches the excluded jira_get_issue (read) and jira_create_issue (write) via tools/call. The test issue it creates is deleted automatically on exit.

Credit

Discovered by Francisco Rosales of Manifold Security

🎯 Affected products1

  • pip/mcp-atlassian:< 0.22.0

🔗 References (6)