VulnWatch VulnWatch
← Back to dashboard
Critical github · GHSA-wrhw-j3f9-8vc6

[mcp-atlassian] Authentication bypass in HTTP transport: AtlassianOpaqueTokenVerifier accepts any non-empty token

Published Sep 22, 2026 CVSS 10.0

Description

mcp-atlassian deploys in two common patterns:

Pattern A (single-user, server-side credentials): operator sets JIRA_USERNAME + JIRA_API_TOKEN (or CONFLUENCE_USERNAME + CONFLUENCE_API_TOKEN) in environment variables. Server uses these to call Jira/Confluence. This is the documented quickstart pattern.

Pattern B (multi-user, OAuth or per-request PAT): operator sets up OAuth proxy or accepts per-user tokens via Authorization or service headers.

The authentication mechanism in HTTP transport has two issues that combine to permit unauthenticated access to Pattern A deployments:

  1. AtlassianOpaqueTokenVerifier.verify_token() at src/mcp_atlassian/utils/token_verifier.py accepts any non-empty string as a valid token:

    async def verify_token(self, token: str) -> AccessToken | None:
        if not token:
            return None
        scopes = self.required_scopes or []
        return AccessToken(
            token=token,
            client_id="atlassian",
            scopes=scopes,
            expires_at=int(time.time()) + 86400 * 30,
        )
    

    The docstring documents this: "we accept non-empty tokens and attach the required scopes."

  2. The default deployment does NOT enable the OAuth proxy auth provider (OAUTH_PROXY_ENABLE_ENV defaults to false; main.py:726). When _build_auth_provider() returns None, FastMCP HTTP transport accepts requests with no authentication challenge.

  3. UserTokenMiddleware._parse_auth_header (main.py:601-664) extracts tokens from Authorization headers and stores them in scope state. If NO Authorization header is present (main.py:584-595), the middleware does not reject the request — it simply does not populate user_atlassian_token.

  4. JiraFetcher / ConfluenceFetcher fall back to JiraConfig.from_env() when no user-supplied token is in scope state. from_env() reads JIRA_API_TOKEN and JIRA_USERNAME from environment and uses them as the API credentials.

Composition: an attacker who reaches the HTTP transport (e.g., server exposed on a port reachable from attacker — direct bind, Docker port mapping, reverse proxy without auth, container in a network the attacker joined) can:

  • Send no Authorization header at all, OR
  • Send any garbage Bearer token

Either request reaches tool handlers. The tool handlers, finding no user-supplied token, use the server's env-var credentials to call Jira / Confluence. The attacker has full operator-level access to the operator's Atlassian instance.

This is the same vulnerability class as CVE-2026-27825 (Arctic Wolf, unauthenticated RCE+SSRF in Atlassian MCP). The previous CVE was for a different code path; this report concerns the auth verifier and middleware behavior present in the current main branch.

**Steps to Reproduce**

Source-level demonstration:

1. Verify the verifier accepts arbitrary tokens:

       cd src/
       python -c "
       import asyncio
       from mcp_atlassian.utils.token_verifier import AtlassianOpaqueTokenVerifier
       v = AtlassianOpaqueTokenVerifier(required_scopes=['read:jira-work'])
       result = asyncio.run(v.verify_token('anything-at-all'))
       print('Accepted:', result is not None)
       print('Token stored:', result.token if result else None)
       print('Scopes granted:', result.scopes if result else None)
       "

   Expected:
       Accepted: True
       Token stored: anything-at-all
       Scopes granted: ['read:jira-work']

End-to-end (researcher's own Atlassian sandbox):

1. Start mcp-atlassian in HTTP mode against a researcher-owned Atlassian
   Cloud instance with JIRA_API_TOKEN configured:

       export JIRA_URL=https://researcher.atlassian.net
       export [email protected]
       export JIRA_API_TOKEN=

Affected AI Products

mcp server
Get the weekly digest. Every Monday: top AI security stories of the week. Free.