CVE-2026-77268

GHSA-4596-2p6p-28cv MEDIUM
Published September 22, 2026

## Summary The OAuth token fallback file storage in `OAuthConfig._save_tokens_to_file()` creates token files containing access tokens, refresh tokens, and cloud IDs with default filesystem permissions (typically `0644` on Linux, world-readable). Any local user on a shared system can read these...

Full CISO analysis pending enrichment.

What systems are affected?

Package Ecosystem Vulnerable Range Patched
MCP Atlassian pip < 0.22.0 0.22.0
1 dependents 85% patched ~3d to patch Full package profile →

Do you use MCP Atlassian? You're affected.

How severe is it?

CVSS 3.1
5.5 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
N/A

What is the attack surface?

AV AC PR UI S C I A
AV Local
AC Low
PR Low
UI None
S Unchanged
C High
I None
A None

What should I do?

Patch available

Update MCP Atlassian to version 0.22.0

Which compliance frameworks are affected?

Compliance analysis pending. Sign in for full compliance mapping when available.

Frequently Asked Questions

What is CVE-2026-77268?

## Summary The OAuth token fallback file storage in `OAuthConfig._save_tokens_to_file()` creates token files containing access tokens, refresh tokens, and cloud IDs with default filesystem permissions (typically `0644` on Linux, world-readable). Any local user on a shared system can read these files to obtain full Atlassian API credentials, enabling unauthorized access to the victim's Jira and Confluence data. ## Details The vulnerability exists in `src/mcp_atlassian/utils/oauth.py` in the `_save_tokens_to_file` method. **Step 1 -- Directory created without restrictive permissions:** At line 402-403, the token directory is created with `mkdir(exist_ok=True)` which uses the default umask (typically creating directories with mode `0755`): ```python # src/mcp_atlassian/utils/oauth.py:402-403 token_dir = Path.home() / ".mcp-atlassian" token_dir.mkdir(exist_ok=True) ``` **Step 2 -- Token file written with default permissions:** At line 417-418, the token file containing sensitive credentials is written using `open()` with no explicit mode, inheriting default umask permissions (typically `0644` on Linux): ```python # src/mcp_atlassian/utils/oauth.py:406-418 token_path = token_dir / f"oauth-{self.client_id}.json" if token_data is None: token_data = { "refresh_token": self.refresh_token, "access_token": self.access_token, "expires_at": self.expires_at, "cloud_id": self.cloud_id, "base_url": self.base_url, } with open(token_path, "w") as f: json.dump(token_data, f) ``` **Step 3 -- The file contains full API credentials:** The token file contains: - `access_token`: A valid OAuth access token for the Atlassian API - `refresh_token`: Can be exchanged for new access tokens indefinitely - `cloud_id`: Identifies the target Atlassian Cloud instance - `base_url`: The target Data Center instance URL **No `os.chmod` or `os.fchmod` is called** anywhere after file creation. The primary storage via `keyring` (line 373) is secure, but the fallback file storage at line 386 is always written in addition to keyring (line 386: `self._save_tokens_to_file(token_data)`). When keyring fails (common in headless/container/CI environments), the fallback becomes the only storage. ## PoC ```bash # Step 1: Victim runs mcp-atlassian with OAuth and completes the flow. # This creates the token file. # Step 2: As any other user on the same system, read the token file: cat /home/victim/.mcp-atlassian/oauth-*.json # Expected output (sensitive credentials in plaintext): # {"refresh_token": "eyJ...", "access_token": "eyJ...", "expires_at": 1741234567.0, "cloud_id": "abc-123", "base_url": null} # Step 3: Verify the token works: curl -H "Authorization: Bearer <stolen_access_token>" \ "https://api.atlassian.com/ex/jira/<stolen_cloud_id>/rest/api/3/myself" # Step 4: Use the refresh token to get a new access token: curl -X POST "https://auth.atlassian.com/oauth/token" \ -d "grant_type=refresh_token" \ -d "client_id=<from_env>" \ -d "client_secret=<from_env>" \ -d "refresh_token=<stolen_refresh_token>" ``` **Verify file permissions (on Linux/macOS):** ```bash ls -la ~/.mcp-atlassian/ # drwxr-xr-x 2 user user 4096 Mar 10 12:00 . # -rw-r--r-- 1 user user 256 Mar 10 12:00 oauth-abc123.json # ^^ ^^ ^^ # world-readable! ``` ## Impact - **Credential theft**: Any local user can read the OAuth tokens and impersonate the victim on their Atlassian Cloud/Data Center instance. - **Persistent access**: The refresh token allows the attacker to generate new access tokens indefinitely, even after the original access token expires. - **Full API access**: The stolen tokens grant the same API permissions as the victim, including reading/writing Jira issues, Confluence pages, and potentially sensitive project data. - **Affected environments**: Shared servers, CI/CD runners, multi-user workstations, and containerized deployments where the fallback file storage is used (keyring unavailable). ## Recommended Fix **1. Set restrictive permissions on the directory and file:** ```python # src/mcp_atlassian/utils/oauth.py import os import stat def _save_tokens_to_file(self, token_data: dict | None = None) -> None: """Save the tokens to a file as fallback storage.""" try: token_dir = Path.home() / ".mcp-atlassian" token_dir.mkdir(exist_ok=True, mode=0o700) token_path = token_dir / f"oauth-{self.client_id}.json" if token_data is None: token_data = { "refresh_token": self.refresh_token, "access_token": self.access_token, "expires_at": self.expires_at, "cloud_id": self.cloud_id, "base_url": self.base_url, } # Open with restrictive permissions (owner-only read/write) fd = os.open( str(token_path), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR, # 0o600 ) try: with os.fdopen(fd, "w") as f: json.dump(token_data, f) except Exception: os.close(fd) raise logger.debug(f"Saved OAuth tokens to file {token_path} (fallback storage)") except Exception as e: logger.error(f"Failed to save tokens to file: {e}") ``` **2. Additionally, fix the directory permissions for existing installations:** ```python # In __init__ or from_env, ensure existing directories are tightened token_dir = Path.home() / ".mcp-atlassian" if token_dir.exists(): os.chmod(str(token_dir), 0o700) ```

Is CVE-2026-77268 actively exploited?

No confirmed active exploitation of CVE-2026-77268 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-77268?

Update to patched version: MCP Atlassian 0.22.0.

What is the CVSS score for CVE-2026-77268?

CVE-2026-77268 has a CVSS v3.1 base score of 5.5 (MEDIUM).

What are the technical details?

Original Advisory

## Summary The OAuth token fallback file storage in `OAuthConfig._save_tokens_to_file()` creates token files containing access tokens, refresh tokens, and cloud IDs with default filesystem permissions (typically `0644` on Linux, world-readable). Any local user on a shared system can read these files to obtain full Atlassian API credentials, enabling unauthorized access to the victim's Jira and Confluence data. ## Details The vulnerability exists in `src/mcp_atlassian/utils/oauth.py` in the `_save_tokens_to_file` method. **Step 1 -- Directory created without restrictive permissions:** At line 402-403, the token directory is created with `mkdir(exist_ok=True)` which uses the default umask (typically creating directories with mode `0755`): ```python # src/mcp_atlassian/utils/oauth.py:402-403 token_dir = Path.home() / ".mcp-atlassian" token_dir.mkdir(exist_ok=True) ``` **Step 2 -- Token file written with default permissions:** At line 417-418, the token file containing sensitive credentials is written using `open()` with no explicit mode, inheriting default umask permissions (typically `0644` on Linux): ```python # src/mcp_atlassian/utils/oauth.py:406-418 token_path = token_dir / f"oauth-{self.client_id}.json" if token_data is None: token_data = { "refresh_token": self.refresh_token, "access_token": self.access_token, "expires_at": self.expires_at, "cloud_id": self.cloud_id, "base_url": self.base_url, } with open(token_path, "w") as f: json.dump(token_data, f) ``` **Step 3 -- The file contains full API credentials:** The token file contains: - `access_token`: A valid OAuth access token for the Atlassian API - `refresh_token`: Can be exchanged for new access tokens indefinitely - `cloud_id`: Identifies the target Atlassian Cloud instance - `base_url`: The target Data Center instance URL **No `os.chmod` or `os.fchmod` is called** anywhere after file creation. The primary storage via `keyring` (line 373) is secure, but the fallback file storage at line 386 is always written in addition to keyring (line 386: `self._save_tokens_to_file(token_data)`). When keyring fails (common in headless/container/CI environments), the fallback becomes the only storage. ## PoC ```bash # Step 1: Victim runs mcp-atlassian with OAuth and completes the flow. # This creates the token file. # Step 2: As any other user on the same system, read the token file: cat /home/victim/.mcp-atlassian/oauth-*.json # Expected output (sensitive credentials in plaintext): # {"refresh_token": "eyJ...", "access_token": "eyJ...", "expires_at": 1741234567.0, "cloud_id": "abc-123", "base_url": null} # Step 3: Verify the token works: curl -H "Authorization: Bearer <stolen_access_token>" \ "https://api.atlassian.com/ex/jira/<stolen_cloud_id>/rest/api/3/myself" # Step 4: Use the refresh token to get a new access token: curl -X POST "https://auth.atlassian.com/oauth/token" \ -d "grant_type=refresh_token" \ -d "client_id=<from_env>" \ -d "client_secret=<from_env>" \ -d "refresh_token=<stolen_refresh_token>" ``` **Verify file permissions (on Linux/macOS):** ```bash ls -la ~/.mcp-atlassian/ # drwxr-xr-x 2 user user 4096 Mar 10 12:00 . # -rw-r--r-- 1 user user 256 Mar 10 12:00 oauth-abc123.json # ^^ ^^ ^^ # world-readable! ``` ## Impact - **Credential theft**: Any local user can read the OAuth tokens and impersonate the victim on their Atlassian Cloud/Data Center instance. - **Persistent access**: The refresh token allows the attacker to generate new access tokens indefinitely, even after the original access token expires. - **Full API access**: The stolen tokens grant the same API permissions as the victim, including reading/writing Jira issues, Confluence pages, and potentially sensitive project data. - **Affected environments**: Shared servers, CI/CD runners, multi-user workstations, and containerized deployments where the fallback file storage is used (keyring unavailable). ## Recommended Fix **1. Set restrictive permissions on the directory and file:** ```python # src/mcp_atlassian/utils/oauth.py import os import stat def _save_tokens_to_file(self, token_data: dict | None = None) -> None: """Save the tokens to a file as fallback storage.""" try: token_dir = Path.home() / ".mcp-atlassian" token_dir.mkdir(exist_ok=True, mode=0o700) token_path = token_dir / f"oauth-{self.client_id}.json" if token_data is None: token_data = { "refresh_token": self.refresh_token, "access_token": self.access_token, "expires_at": self.expires_at, "cloud_id": self.cloud_id, "base_url": self.base_url, } # Open with restrictive permissions (owner-only read/write) fd = os.open( str(token_path), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR, # 0o600 ) try: with os.fdopen(fd, "w") as f: json.dump(token_data, f) except Exception: os.close(fd) raise logger.debug(f"Saved OAuth tokens to file {token_path} (fallback storage)") except Exception as e: logger.error(f"Failed to save tokens to file: {e}") ``` **2. Additionally, fix the directory permissions for existing installations:** ```python # In __init__ or from_env, ensure existing directories are tightened token_dir = Path.home() / ".mcp-atlassian" if token_dir.exists(): os.chmod(str(token_dir), 0o700) ```

Weaknesses (CWE)

CWE-732 — Incorrect Permission Assignment for Critical Resource: The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors.

  • [Implementation] When using a critical resource such as a configuration file, check to see if the resource has insecure permissions (such as being modifiable by any regular user) [REF-62], and generate an error or even exit the software if there is a possibility that the resource could have been modified by an unauthorized party.
  • [Architecture and Design] Divide the software into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully defining distinct user groups, privileges, and/or roles. Map these against data, functionality, and the related resources. Then set the permissions accordingly. This will allow you to maintain more fine-grained control over your resources. [REF-207]

Source: MITRE CWE corpus.

CVSS Vector

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

Timeline

Published
September 22, 2026
Last Modified
September 22, 2026
First Seen
September 23, 2026

Related Vulnerabilities