[mcp-atlassian] Authentication bypass in HTTP transport: AtlassianOpaqueTokenVerifier accepts any non-empty token
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:
-
AtlassianOpaqueTokenVerifier.verify_token() at
src/mcp_atlassian/utils/token_verifier.pyaccepts 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."
-
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. -
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 populateuser_atlassian_token. -
JiraFetcher / ConfluenceFetcher fall back to
JiraConfig.from_env()when no user-supplied token is in scope state.from_env()readsJIRA_API_TOKENandJIRA_USERNAMEfrom 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=