PT-2026-105837 · Pypi · Mcp-Attlasian

Publicado

2026-10-01

·

Atualizado

2026-10-01

CVSS v3.1

5.5

Média

VetorAV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

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)

Correção

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Identificadores relacionados

PYSEC-2026-4082

Produtos afetados

Mcp-Attlasian