GHSA-P6HP-93WP-FH6P
Vulnerability from github – Published: 2026-09-22 20:35 – Updated: 2026-09-22 20:35Summary
mcp-atlassian exposes an MCP tool confluence_upload_attachment whose file_path argument is passed directly to open(file_path, "rb") without any path validation. An attacker able to invoke the tool can read arbitrary files readable by the server process and exfiltrate them into a multipart upload directed at an attacker-controlled Confluence host. In the default streamable-http transport the server binds 0.0.0.0 with no built-in authentication, making this remotely exploitable without credentials.
This is the read-side symmetric twin of GHSA-xjgw-4wvw-rgm4 / CVE-2026-27825 (fixed in v0.17.0). The v0.17.0 patch only covered the download/write path; the upload path that reads local files was left unguarded.
Details
Vulnerable sink
src/mcp_atlassian/confluence/attachments.py:477
with open(file_path, "rb") as fp:
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
file_path is attacker-controlled end-to-end.
Taint source
src/mcp_atlassian/servers/confluence.py:1290-1369, tool definition at :1307:
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
No Pydantic pattern=, no validator, no validate_safe_path() call.
Call chain
- MCP client invokes
confluence_upload_attachment(page_id, file_path, ...) - Server handler forwards to
ConfluenceFetcher.upload_attachment(file_path) _upload_attachment_direct(file_path)callsopen(file_path, "rb")- File bytes are streamed in the multipart body of
POST /wiki/rest/api/content/{page_id}/child/attachmentto the configured Confluence base URL — which the attacker also controls (they providedCONFLUENCE_URLvia env/config or target a server they already control).
Asymmetry with the patched download path
attachments.py:223(download) — callsvalidate_safe_path(local_path)beforeopen(..., "wb")attachments.py:272(download) — callsvalidate_safe_path(local_path)beforeopen(..., "wb")attachments.py:477(upload) — no validation
The check_write_access decorator does not help: it only gates READ_ONLY_MODE (default false) and is unrelated to filesystem path safety.
Default exposure
src/mcp_atlassian/__init__.py:151 and :360 — default transport is streamable-http binding HOST=0.0.0.0 with no auth layer. Any network-reachable attacker can call MCP tools directly.
Severity
Primary (default streamable-http deployment)
- Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
- Score: 9.3 Critical
- Rationale: network-reachable, unauthenticated, scope-changed because files outside the MCP server's intended resource boundary (Confluence attachments) are exfiltrated.
Alternative (stdio-only deployment, conservative)
- Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
- Score: 6.6 High
- Rationale: local attacker controlling the MCP client context.
Maintainer should pick the vector that reflects the documented default deployment.
CWE
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Affected
- Product:
sooperset/mcp-atlassian - Affected versions: >= 0.17.0, <= HEAD (
d8bc78698a63cb6b321c7ca796d6329d448f7f6d) - Note: v0.17.0 is the fix commit for GHSA-xjgw-4wvw-rgm4 but only addressed the write-side. This read-side twin has existed since that release and remains unpatched on
main. - Patched versions: none at time of disclosure
Proof of Concept
Fully reproduced twice end-to-end against a local stdlib HTTP stub acting as the Confluence API, driven by a real MCP stdio client (mcp.ClientSession + stdio_client) spawning the unmodified mcp-atlassian server at HEAD.
Permalinks (commit-pinned)
- Sink: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L477
- Source (tool def): https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/servers/confluence.py#L1307
- Safe download comparison: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L223
- Default transport bind: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/init.py#L151
Reproduction
- Start mock Confluence stub:
python mock_confluence.py(binds127.0.0.1:8443, logs all multipart bodies) - Launch MCP client against real
mcp-atlassianserver over stdio withCONFLUENCE_URL=http://127.0.0.1:8443 - Call tool:
{
"name": "confluence_upload_attachment",
"arguments": {
"page_id": "123456",
"file_path": "/etc/passwd",
"comment": "poc"
}
}
Run 1 — /etc/passwd
- MCP response:
isError=False,{"message": "Attachment uploaded successfully"} - Stub captured 3339-byte multipart body containing:
root:x:0:0:root:/root:/bin/bash(and full passwd contents)
Run 2 — /etc/hostname
- MCP response:
isError=False, same success envelope - Stub captured 380-byte body containing:
ang3l-pc
Both runs used unmodified server code at commit d8bc78698a63cb6b321c7ca796d6329d448f7f6d. PoC artifacts (mock_confluence.py, mcp_client.py, poc_run1.sh, poc_run2.sh, asymmetry.txt, ENVIRONMENT.md) available on request to maintainers via this advisory thread.
Impact
- Arbitrary file read of anything readable by the server process UID:
/etc/passwd,/etc/shadow(if running as root in container),~/.aws/credentials,~/.ssh/id_rsa,.envfiles, kube service-account tokens at/var/run/secrets/kubernetes.io/serviceaccount/token, application source, database dumps, private keys. - Exfiltration is covert: file bytes transit to the attacker's configured Confluence host inside a normal-looking multipart upload. No error surface; the tool returns success.
- In the default
streamable-http0.0.0.0 deployment, no credentials are required. - Chains trivially with any AI agent that exposes this MCP server to untrusted prompt input — a prompt-injected assistant can be coerced into calling the tool with a sensitive path.
Relationship to GHSA-xjgw-4wvw-rgm4 (CVE-2026-27825)
GHSA-xjgw-4wvw-rgm4 (CVSS 9.1, fixed in v0.17.0) addressed an arbitrary file write in the same attachments.py module: attacker-controlled paths reaching open(..., "wb") on the download side. The fix introduced validate_safe_path() and applied it at lines 223 and 272.
The upload-side counterpart at line 477 was not updated. Same module, same maintainer, same class of bug (unchecked path → open()), opposite direction (read vs write). This advisory reports the incomplete-fix twin.
Remediation
Required
Call validate_safe_path(file_path) at the top of ConfluenceFetcher.upload_attachment and _upload_attachment_direct in src/mcp_atlassian/confluence/attachments.py, mirroring the download path at lines 223 and 272. Reject absolute paths outside a configurable allow-listed upload directory and reject any path containing .. after normalization.
Defense in depth
Tighten the Pydantic tool schema at src/mcp_atlassian/servers/confluence.py:1307:
file_path: Annotated[
str,
Field(
description="Relative path within the configured upload directory",
pattern=r"^(?!/)(?!.*\.\.)[\w\-./]+$",
),
]
This blocks absolute paths and .. at the MCP schema layer before the handler is even entered.
Additional hardening (out of scope but recommended)
- Default
streamable-httpto127.0.0.1instead of0.0.0.0, or require an auth token when bound to a non-loopback interface. - Document that
file_pathmust be confined to an operator-chosen directory and expose that directory via config.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "mcp-atlassian"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.22.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-77262"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T20:35:21Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\n`mcp-atlassian` exposes an MCP tool `confluence_upload_attachment` whose `file_path` argument is passed directly to `open(file_path, \"rb\")` without any path validation. An attacker able to invoke the tool can read arbitrary files readable by the server process and exfiltrate them into a multipart upload directed at an attacker-controlled Confluence host. In the default `streamable-http` transport the server binds `0.0.0.0` with no built-in authentication, making this remotely exploitable without credentials.\n\nThis is the **read-side symmetric twin** of GHSA-xjgw-4wvw-rgm4 / CVE-2026-27825 (fixed in v0.17.0). The v0.17.0 patch only covered the download/write path; the upload path that reads local files was left unguarded.\n\n## Details\n\n### Vulnerable sink\n`src/mcp_atlassian/confluence/attachments.py:477`\n```python\nwith open(file_path, \"rb\") as fp:\n files = {\"file\": (filename, fp, content_type)}\n response = self.confluence.session.post(url, files=files, ...)\n```\n`file_path` is attacker-controlled end-to-end.\n\n### Taint source\n`src/mcp_atlassian/servers/confluence.py:1290-1369`, tool definition at `:1307`:\n```python\nfile_path: Annotated[str, Field(description=\"Absolute path to the file to upload\")]\n```\nNo Pydantic `pattern=`, no validator, no `validate_safe_path()` call.\n\n### Call chain\n1. MCP client invokes `confluence_upload_attachment(page_id, file_path, ...)`\n2. Server handler forwards to `ConfluenceFetcher.upload_attachment(file_path)`\n3. `_upload_attachment_direct(file_path)` calls `open(file_path, \"rb\")`\n4. File bytes are streamed in the multipart body of `POST /wiki/rest/api/content/{page_id}/child/attachment` to the configured Confluence base URL \u2014 which the attacker also controls (they provided `CONFLUENCE_URL` via env/config or target a server they already control).\n\n### Asymmetry with the patched download path\n- `attachments.py:223` (download) \u2014 calls `validate_safe_path(local_path)` before `open(..., \"wb\")`\n- `attachments.py:272` (download) \u2014 calls `validate_safe_path(local_path)` before `open(..., \"wb\")`\n- `attachments.py:477` (upload) \u2014 **no validation**\n\nThe `check_write_access` decorator does not help: it only gates `READ_ONLY_MODE` (default `false`) and is unrelated to filesystem path safety.\n\n### Default exposure\n`src/mcp_atlassian/__init__.py:151` and `:360` \u2014 default transport is `streamable-http` binding `HOST=0.0.0.0` with no auth layer. Any network-reachable attacker can call MCP tools directly.\n\n## Severity\n\n**Primary (default `streamable-http` deployment)**\n- Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N`\n- Score: **9.3 Critical**\n- Rationale: network-reachable, unauthenticated, scope-changed because files outside the MCP server\u0027s intended resource boundary (Confluence attachments) are exfiltrated.\n\n**Alternative (stdio-only deployment, conservative)**\n- Vector: `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N`\n- Score: **6.6 High**\n- Rationale: local attacker controlling the MCP client context.\n\nMaintainer should pick the vector that reflects the documented default deployment.\n\n## CWE\nCWE-22: Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027)\n\n## Affected\n- Product: `sooperset/mcp-atlassian`\n- Affected versions: **\u003e= 0.17.0, \u003c= HEAD (`d8bc78698a63cb6b321c7ca796d6329d448f7f6d`)**\n- Note: v0.17.0 is the fix commit for GHSA-xjgw-4wvw-rgm4 but only addressed the write-side. This read-side twin has existed since that release and remains unpatched on `main`.\n- Patched versions: **none at time of disclosure**\n\n## Proof of Concept\n\nFully reproduced twice end-to-end against a local stdlib HTTP stub acting as the Confluence API, driven by a real MCP stdio client (`mcp.ClientSession` + `stdio_client`) spawning the unmodified `mcp-atlassian` server at HEAD.\n\n### Permalinks (commit-pinned)\n- Sink: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L477\n- Source (tool def): https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/servers/confluence.py#L1307\n- Safe download comparison: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/confluence/attachments.py#L223\n- Default transport bind: https://github.com/sooperset/mcp-atlassian/blob/d8bc78698a63cb6b321c7ca796d6329d448f7f6d/src/mcp_atlassian/__init__.py#L151\n\n### Reproduction\n1. Start mock Confluence stub: `python mock_confluence.py` (binds `127.0.0.1:8443`, logs all multipart bodies)\n2. Launch MCP client against real `mcp-atlassian` server over stdio with `CONFLUENCE_URL=http://127.0.0.1:8443`\n3. Call tool:\n```json\n{\n \"name\": \"confluence_upload_attachment\",\n \"arguments\": {\n \"page_id\": \"123456\",\n \"file_path\": \"/etc/passwd\",\n \"comment\": \"poc\"\n }\n}\n```\n\n### Run 1 \u2014 `/etc/passwd`\n- MCP response: `isError=False`, `{\"message\": \"Attachment uploaded successfully\"}`\n- Stub captured 3339-byte multipart body containing: `root:x:0:0:root:/root:/bin/bash` (and full passwd contents)\n\n### Run 2 \u2014 `/etc/hostname`\n- MCP response: `isError=False`, same success envelope\n- Stub captured 380-byte body containing: `ang3l-pc`\n\nBoth runs used unmodified server code at commit `d8bc78698a63cb6b321c7ca796d6329d448f7f6d`. PoC artifacts (`mock_confluence.py`, `mcp_client.py`, `poc_run1.sh`, `poc_run2.sh`, `asymmetry.txt`, `ENVIRONMENT.md`) available on request to maintainers via this advisory thread.\n\n## Impact\n- Arbitrary file read of anything readable by the server process UID: `/etc/passwd`, `/etc/shadow` (if running as root in container), `~/.aws/credentials`, `~/.ssh/id_rsa`, `.env` files, kube service-account tokens at `/var/run/secrets/kubernetes.io/serviceaccount/token`, application source, database dumps, private keys.\n- Exfiltration is covert: file bytes transit to the attacker\u0027s configured Confluence host inside a normal-looking multipart upload. No error surface; the tool returns success.\n- In the default `streamable-http` 0.0.0.0 deployment, no credentials are required.\n- Chains trivially with any AI agent that exposes this MCP server to untrusted prompt input \u2014 a prompt-injected assistant can be coerced into calling the tool with a sensitive path.\n\n## Relationship to GHSA-xjgw-4wvw-rgm4 (CVE-2026-27825)\nGHSA-xjgw-4wvw-rgm4 (CVSS 9.1, fixed in v0.17.0) addressed an **arbitrary file write** in the same `attachments.py` module: attacker-controlled paths reaching `open(..., \"wb\")` on the download side. The fix introduced `validate_safe_path()` and applied it at lines 223 and 272.\n\n**The upload-side counterpart at line 477 was not updated.** Same module, same maintainer, same class of bug (unchecked path \u2192 `open()`), opposite direction (read vs write). This advisory reports the incomplete-fix twin.\n\n## Remediation\n\n### Required\nCall `validate_safe_path(file_path)` at the top of `ConfluenceFetcher.upload_attachment` and `_upload_attachment_direct` in `src/mcp_atlassian/confluence/attachments.py`, mirroring the download path at lines 223 and 272. Reject absolute paths outside a configurable allow-listed upload directory and reject any path containing `..` after normalization.\n\n### Defense in depth\nTighten the Pydantic tool schema at `src/mcp_atlassian/servers/confluence.py:1307`:\n```python\nfile_path: Annotated[\n str,\n Field(\n description=\"Relative path within the configured upload directory\",\n pattern=r\"^(?!/)(?!.*\\.\\.)[\\w\\-./]+$\",\n ),\n]\n```\nThis blocks absolute paths and `..` at the MCP schema layer before the handler is even entered.\n\n### Additional hardening (out of scope but recommended)\n- Default `streamable-http` to `127.0.0.1` instead of `0.0.0.0`, or require an auth token when bound to a non-loopback interface.\n- Document that `file_path` must be confined to an operator-chosen directory and expose that directory via config.",
"id": "GHSA-p6hp-93wp-fh6p",
"modified": "2026-09-22T20:35:22Z",
"published": "2026-09-22T20:35:21Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-p6hp-93wp-fh6p"
},
{
"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:N/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "MCP Atlassian: Path Traversal / Arbitrary File Read in confluence_upload_attachment MCP tool (incomplete fix of GHSA-xjgw-4wvw-rgm4)"
}
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.