Skip to content

GHSA-wrhw-j3f9-8vc6 CVE-2026-77244 PYSEC-2026-4103

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

Critical PyPI
Severity
10.0 / 10
Critical · CVSS 3.1
Exploit chance (EPSS)
0.35%
Chance of exploitation in the next 30 days. Higher than 28.22% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-287 Improper Authentication
CWE-303 Incorrect Implementation of Authentication Algorithm
CWE-862 Missing Authorization
Fix
0.22.0
Upgrade mcp-atlassian to this version or later

Description

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 JIRA_USERNAME=researcher@example.com
       export JIRA_API_TOKEN=<researcher's-real-token>
       export MCP_TRANSPORT=streamable-http
       export PORT=3000
       # Do NOT set OAUTH_PROXY_ENABLE_ENV — leave it default (false)
       mcp-atlassian

2. 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":"jira_get_issue",
                "arguments":{"issue_key":"PROJ-1"}
              }
            }'

   Expected: returns the Jira issue payload — using the server's
   JIRA_API_TOKEN to authenticate to Atlassian. No client-side token
   provided.

3. 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:

1. Full Jira read access. Every project, every issue, every comment,
   every attachment, every user — using the operator's API token.

2. Full Jira write access. Create, edit, delete issues. Add comments
   under the operator's identity. Move issues across boards. Bulk-edit.

3. Full Confluence read/write access. Same surface — pages, spaces,
   attachments, permissions, restricted spaces visible to the operator's
   identity.

4. 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.

5. Pivot. Attachments often contain credentials, infrastructure
   diagrams, customer data. Confluence pages often store secrets in
   plaintext under the assumption of access control.

6. 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:

  1. When OAUTH_PROXY_ENABLE_ENV is not set, REFUSE to start the HTTP
     transport unless an explicit "single-user mode" flag is set:

       SINGLE_USER_MODE = is_env_truthy("MCP_ATLASSIAN_SINGLE_USER")
       if MCP_TRANSPORT == "streamable-http" and not auth_provider and not SINGLE_USER_MODE:
           raise SystemExit(
               "HTTP transport requires either OAUTH_PROXY_ENABLE=true "
               "or MCP_ATLASSIAN_SINGLE_USER=true (acknowledges that env "
               "credentials will be used for any incoming request)."
           )

  2. Even with SINGLE_USER_MODE, bind the HTTP transport to 127.0.0.1
     by default unless the operator overrides with an explicit
     MCP_ATLASSIAN_BIND_PUBLIC=true.

  3. 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).

  4. 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.

Affected versions

PyPI

PackageVulnerableFixed in
mcp-atlassian
< 0.22.0
0.22.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

mcp-atlassian PyPI → 0.22.0

pip
pip install --upgrade "mcp-atlassian>=0.22.0"
poetry
poetry add "mcp-atlassian>=0.22.0"
uv
uv add "mcp-atlassian>=0.22.0"

Severity breakdown

CVSS 3.1 10.0

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

Weakness

CWE-287: Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

How to prevent it
  • Architecture and Design: Use an authentication framework or library such as the OWASP ESAPI Authentication feature.

CWE-303: Incorrect Implementation of Authentication Algorithm

The requirements for the product dictate the use of an established authentication algorithm, but the implementation of the algorithm is incorrect.

CWE-862: Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

How to prevent it
  • Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • Architecture and Design: Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
  • Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
  • System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-g2r2-3j32-j27x
MCP Atlassian: Reflected XSS in OAuth Setup Callback Handler
Medium Also affects mcp-atlassian
GHSA-h7wj-5v37-59r2
MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read (incomplete fix for CVE-2026-27825)
Medium Also affects mcp-atlassian
GHSA-4596-2p6p-28cv
MCP Atlassian: Insecure File Permissions on OAuth Token Storage
Medium Also affects mcp-atlassian
GHSA-mfv2-4wvm-9pgp
MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read and exfiltration via MCP tool call
Medium Also affects mcp-atlassian
GHSA-p6hp-93wp-fh6p
MCP Atlassian: Path Traversal / Arbitrary File Read in confluence_upload_attachment MCP tool (incomplete fix of GHSA-xjgw-4wvw-rgm4)
High Also affects mcp-atlassian
GHSA-6cr4-ccf3-x7h4
MCP Atlassian: Arbitrary file read via confluence_upload_attachment allows exfiltration of server credentials
High Also affects mcp-atlassian
GHSA-mrq8-fv7v-hhjg
MCP Atlassian: HTTP upload tools accept arbitrary server-local file paths
Medium Also affects mcp-atlassian
GHSA-2xj6-xx86-cwwc
MCP Atlassian: Arbitrary File Read & Exfiltration (Confused Deputy) in JIRA update_issue
High Also affects mcp-atlassian
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor