{"id":"PYSEC-2026-4081","summary":"MCP Atlassian: ENABLED_TOOLS / Toolset authorization bypass","details":"### Summary\n\n`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.\n\n`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.\n\n### Impact\n\nAny 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.\n\nThe 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.\n\n### Details\nIn `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.\n\n\n### Proof of Concept\n\nAll calls are issued against the HTTP transport, with `ENABLED_TOOLS=jira_search` configured - only `jira_search` should be reachable.\n\nStep 1 - negative control: tools/list correctly filters by ENABLED_TOOLS:\n```\nreq → tools/list\n← { \"tools\": [ { \"name\": \"jira_search\" } ] }   # only 1 tool; jira_get_issue / jira_create_issue absent\n```\n\nStep 2 - confidentiality bypass: tools/call dispatches jira_get_issue despite its exclusion from the list:\n```\nreq → tools/call jira_get_issue { \"issue_key\": \"SEC-1\" }\n← { ... issue fields (summary, status, ...) ... }   # executed; not blocked\n```\n\nStep 3 - integrity bypass: tools/call dispatches the write tool jira_create_issue:\n```\nreq → tools/call jira_create_issue\n         { \"project_key\": \"SEC\", \"summary\": \"[PoC] ENABLED_TOOLS bypass\", \"issue_type\": \"Task\" }\n← { ... \"key\": \"SEC-\u003cn\u003e\" ... }   # issue created despite ENABLED_TOOLS=jira_search\n```\n\nStep 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.\n\n**Local reproduction**\n\nExtract `enabled_tools_bypass_tool_authorization.zip`:\n```\n# Fill Atlassian credentials in docker-compose.yml (ENABLED_TOOLS=jira_search is preset);\n# ensure issue SEC-1 exists, or update the project/issue key in poc.sh\ndocker compose up -d        # mcp-atlassian, streamable-http, 0.0.0.0, ENABLED_TOOLS=jira_search\n./poc.sh                    # exits 0 on success\ndocker compose down -v\n```\n\n\u003eRequires 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.\n\n### Credit\nDiscovered by [Francisco Rosales](https://www.linkedin.com/in/francisco-rosales-celis/) of [Manifold Security](https://manifold.security/)","aliases":["CVE-2026-77243","GHSA-3r68-hf9h-887v"],"modified":"2026-10-01T17:45:10.995873898Z","published":"2026-10-01T16:38:35.193604Z","references":[{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-3r68-hf9h-887v"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-77243"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/pull/1448"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460"},{"type":"PACKAGE","url":"https://github.com/sooperset/mcp-atlassian"},{"type":"WEB","url":"https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0"},{"type":"PACKAGE","url":"https://pypi.org/project/mcp-atlassian"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-3r68-hf9h-887v"}],"affected":[{"package":{"name":"mcp-atlassian","ecosystem":"PyPI","purl":"pkg:pypi/mcp-atlassian"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.22.0"}]}],"versions":["0.1.1","0.1.10","0.1.11","0.1.12","0.1.13","0.1.14","0.1.15","0.1.16","0.1.2","0.1.3","0.1.4","0.1.6","0.1.7","0.1.8","0.1.9","0.10.0","0.10.1","0.10.2","0.10.3","0.10.4","0.10.5","0.10.6","0.11.0","0.11.1","0.11.10","0.11.11","0.11.12","0.11.2","0.11.2a2","0.11.3","0.11.4","0.11.5","0.11.6","0.11.7","0.11.8","0.11.9","0.12.0","0.13.0","0.13.1","0.14.0","0.14.1","0.14.2","0.14.3","0.15.0","0.16.0","0.16.1","0.17.0","0.18.0","0.18.1","0.19.0","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.20.0","0.20.1","0.21.0","0.21.1","0.3.0","0.3.1","0.4.0","0.5.0","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.9.0"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/mcp-atlassian/PYSEC-2026-4081.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}