CVE-2026-77244
Description
mcp-atlassian deploys in two common patterns:
Pattern A (single-user, server-side credentials): operator sets
JIRAUSERNAME + JIRAAPITOKEN (or CONFLUENCEUSERNAME + CONFLUENCEAPITOKEN)
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/mcpatlassian/utils/tokenverifier.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."
- The default deployment does NOT enable the OAuth proxy auth provider
(OAUTHPROXYENABLE_ENV defaults to false; main.py:726). When
buildauth_provider() returns None, FastMCP HTTP transport accepts
requests with no authentication challenge.
UserTokenMiddleware.parseauth_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
useratlassiantoken.
- JiraFetcher / ConfluenceFetcher fall back to
JiraConfig.from_env()
when no user-supplied token is in scope state. from_env() reads
JIRAAPITOKEN 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:
- Verify the verifier accepts arbitrary tokens:
cd src/
python -c "
import asyncio
from mcpatlassian.utils.tokenverifier 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):
- Start mcp-atlassian in HTTP mode against a researcher-owned Atlassian
Cloud instance with JIRAAPITOKEN configured:
export JIRA_URL=https://researcher.atlassian.net
export JIRA_USERNAME=researcher@example.com
export JIRAAPITOKEN=<researcher's-real-token>
export MCP_TRANSPORT=streamable-http
export PORT=3000
# Do NOT set OAUTHPROXYENABLE_ENV — leave it default (false)
mcp-atlassian
- From another machine (or curl on localhost), with no auth:
curl -X POST http://localhost:3000/mcp \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-d '{
"jsonrpc":"2.0", "id":1, "method":"tools/call",
"params":{
"name":"jiragetissue",
"arguments":{"issue_key":"PROJ-1"}
}
}'
Expected: returns the Jira issue payload — using the server's
JIRAAPITOKEN to authenticate to Atlassian. No client-side token
provided.
- Optional: same call with a garbage Bearer for completeness:
curl ... -H "Authorization: Bearer anything-at-all" ...
Same result.
Impact:
Attacker profile: any party with network reach to the HTTP transport.
No credentials, no prior account, no privileged position required.
Typical deployment patterns at risk:
- Docker compose with port exposed (very common in mcp-atlassian's
docs and community deployments)
- Cloud-deployed MCP server behind a load balancer where the LB
doesn't enforce auth (delegates to the application)
- Internal corporate network where any employee can reach the server
- Misconfigured Kubernetes ingress
- Tunneled MCP server via ngrok / Cloudflare Tunnel for development
that gets left exposed
Security impact after exploitation:
- Full Jira read access. Every project, every issue, every comment,
every attachment, every user — using the operator's API token.
- Full Jira write access. Create, edit, delete issues. Add comments
under the operator's identity. Move issues across boards. Bulk-edit.
- Full Confluence read/write access. Same surface — pages, spaces,
attachments, permissions, restricted spaces visible to the operator's
identity.
- Audit trail names the operator. Every API call is signed with the
operator's token. From Atlassian's logging side, the operator is the
actor — covering the attacker's tracks and shifting blame.
- Pivot. Attachments often contain credentials, infrastructure
diagrams, customer data. Confluence pages often store secrets in
plaintext under the assumption of access control.
- Persistence. Attacker can create new Jira webhooks, automation rules,
or Confluence integrations that survive beyond the MCP session.
CVE-2026-27825 (Arctic Wolf, May 2026) was scored CVSS 9.8 Critical for
unauth RCE+SSRF in this same code surface. This report is the auth-bypass
component of the same class against the current main branch.
Suggested Fix
The most direct fix is the standard MCP-server-with-env-creds pattern:
- When OAUTHPROXYENABLE_ENV is not set, REFUSE to start the HTTP
transport unless an explicit "single-user mode" flag is set:
SINGLEUSERMODE = isenvtruthy("MCPATLASSIANSINGLE_USER")
if MCPTRANSPORT == "streamable-http" and not authprovider and not SINGLEUSERMODE:
raise SystemExit(
"HTTP transport requires either OAUTHPROXYENABLE=true "
"or MCPATLASSIANSINGLE_USER=true (acknowledges that env "
"credentials will be used for any incoming request)."
)
- Even with SINGLEUSERMODE, bind the HTTP transport to 127.0.0.1
by default unless the operator overrides with an explicit
MCPATLASSIANBIND_PUBLIC=true.
- Document the multi-tenant pattern as requiring OAuth proxy or
per-request user-token middleware with a verifier that actually
verifies (not the opaque-accept-anything stub).
- Replace AtlassianOpaqueTokenVerifier with a verifier that performs
a token-info or whoami call to Atlassian. The fact that Atlassian
tokens are opaque does not preclude verification — a
/rest/api/3/myself call validates the token and returns the
associated user, which the verifier can attach to the AccessToken's
scopes and user_id fields.
Defense in depth: the README quickstart should not encourage exposing
the HTTP transport without auth. The docker-compose.yml in the repo
should bind to 127.0.0.1 only by default.
Package Versions Affected
Automatically patch vulnerabilities without upgrading
CVSS Version



Related Resources
References
https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-wrhw-j3f9-8vc6, https://nvd.nist.gov/vuln/detail/CVE-2026-77244, https://github.com/sooperset/mcp-atlassian/pull/1448, https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460, https://github.com/sooperset/mcp-atlassian, https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0