Skip to content

GHSA-g5xv-mhgm-v5f6 CVE-2026-77250 PYSEC-2026-4094

MCP Atlassian: OAuth fallback token storage writes plaintext access and refresh tokens with group-readable permissions

Medium PyPI
Severity
6.1 / 10
Medium · CVSS 3.1
Exploit chance (EPSS)
0.11%
Chance of exploitation in the next 30 days. Higher than 1.51% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-312 Cleartext Storage of Sensitive Information
Fix
0.22.0
Upgrade mcp-atlassian to this version or later

Description

Summary

When OAuth tokens are saved, MCP Atlassian always writes a plaintext fallback copy under ~/.mcp-atlassian/oauth-<client_id>.json. The fallback file is created with the process default umask rather than restrictive permissions. In this environment the file was created as mode 0664, exposing access and refresh tokens to same-group local users and any process that can read the home directory.

Details

OAuthConfig._save_tokens() stores OAuth token data in keyring, but it also unconditionally maintains a plaintext file fallback for backwards compatibility in src/mcp_atlassian/utils/oauth.py:350-386. If keyring saving fails it also falls back to the same file path in src/mcp_atlassian/utils/oauth.py:387-391.

The fallback writer creates ~/.mcp-atlassian and then writes oauth-<client_id>.json with a normal open(token_path, "w") call in src/mcp_atlassian/utils/oauth.py:392-420. No mode=0o600, os.open(..., 0o600), chmod, or owner-only directory permission is applied. The file contains both access_token and refresh_token (src/mcp_atlassian/utils/oauth.py:360-367) and is later loaded from the same plaintext path in src/mcp_atlassian/utils/oauth.py:450-470.

The security policy warns that OAuth client credentials and secrets should not be exposed (SECURITY.md:39-44), but the current implementation creates a persistent plaintext token copy even when keyring succeeds.

PoC

The following safe local proof uses a temporary HOME and mocked keyring writes. It creates and deletes only temporary files.

uv run python - <<'PY'
import json, os, shutil, stat, tempfile
from pathlib import Path
from unittest.mock import patch
from mcp_atlassian.utils.oauth import OAuthConfig

home = tempfile.mkdtemp(prefix='mcp-atlassian-oauth-poc-')
old_home = os.environ.get('HOME')
os.environ['HOME'] = home
try:
    cfg = OAuthConfig(client_id='poc-client', client_secret='client-secret', redirect_uri='http://localhost/callback', scope='offline_access', cloud_id='cloud-id')
    cfg.access_token = 'poc-access-token'
    cfg.refresh_token = 'poc-refresh-token'
    cfg.expires_at = 2000000000
    with patch('keyring.set_password', return_value=None):
        cfg._save_tokens()
    token_file = Path(home) / '.mcp-atlassian' / 'oauth-poc-client.json'
    mode = stat.S_IMODE(token_file.stat().st_mode)
    data = json.loads(token_file.read_text())
    print(json.dumps({
        'token_file_exists': token_file.exists(),
        'token_file_mode_octal': oct(mode),
        'contains_access_token': data.get('access_token') == 'poc-access-token',
        'contains_refresh_token': data.get('refresh_token') == 'poc-refresh-token',
        'token_file_path': str(token_file),
    }, indent=2, sort_keys=True))
finally:
    if old_home is not None:
        os.environ['HOME'] = old_home
    else:
        os.environ.pop('HOME', None)
    shutil.rmtree(home)
PY

Observed output from this environment:

{
  "contains_access_token": true,
  "contains_refresh_token": true,
  "token_file_exists": true,
  "token_file_mode_octal": "0o664",
  "token_file_path": "/tmp/mcp-atlassian-oauth-poc-9m9wvktp/.mcp-atlassian/oauth-poc-client.json"
}

The proof confirms that a plaintext file containing both access and refresh tokens is created and is not owner-only.

Impact

A local user, container sidecar, compromised dependency, backup job, or other process with filesystem read access to the account's home directory can recover OAuth access and refresh tokens. Refresh tokens can allow continued Atlassian API access until revoked or expired, depending on the OAuth app and token policy. In shared hosts, Kubernetes volumes, developer workstations, and CI runners, this can lead to persistent Atlassian account compromise.

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 6.1

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

Attack vector
Local
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
Low
Availability
None

Weakness

CWE-312: Cleartext Storage of Sensitive Information

The product stores sensitive information in cleartext within a resource that might be accessible to another control sphere.

How to prevent it
  • Implementation, System Configuration, Operation: When storing data in the cloud (e.g., S3 buckets, Azure blobs, Google Cloud Storage, etc.), use the provider's controls to encrypt the data at rest. [REF-1297] [REF-1299] [REF-1301]
  • Implementation, System Configuration, Operation: In some systems/environments such as cloud, the use of "double encryption" (at both the software and hardware layer) might be required, and the developer might be solely responsible for both layers, instead of shared responsibility with the administrator of the broader system/environment.

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