GitHub Advisoryadvisory · PyPI

GHSA-4596-2p6p-28cv MCP Atlassian: Insecure File Permissions on OAuth Token Storage

MEDIUM Published 2026-09-22 Modified 2026-09-22 Official page ↗
Aliases
CVE-2026-77268
Ecosystem
PyPI
Packages
mcp-atlassian
Severity
Moderate
CVSS vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
CWE-732
Reviewed
2026-09-22

Affected packages

• PyPI mcp-atlassian: < 0.22.0 → fixed in 0.22.0

Description

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 " \ "https://api.atlassian.com/ex/jira//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=" \ -d "client_secret=" \ -d "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) ```

Related

Alias · CVE (1)

  • CVE-2026-77268 MCP Atlassian is a Model Context Protocol (MCP) server for Atlassian products (Confluence and Jira). vulnerability

Weakness · CWE (1)

  • CWE-732 Incorrect Permission Assignment for Critical Resource weakness

References