CVE-2026-77262

CVSS 8.6 · High Patched
Type
Vulnerability research
Published
Reading time
8 min read
Author
rootzero
Lire en français
Lire en français →

CVE-2026-77262: arbitrary file read in MCP Atlassian through confluence_upload_attachment

MCP Atlassian opened any file_path passed to its Confluence upload tool, so a caller could read host files and push them out as attachments.

For your decision

Impact
A file readable by the MCP server can be uploaded as a Confluence attachment.
Conditions
The caller must be able to use the upload tool and write to the Confluence destination. A manipulated agent is a separate scenario.
Recommended action
Install MCP Atlassian 0.22.0 or later and review files accessible to the MCP process.

Advisory facts

Package
pypi · mcp-atlassian
Affected versions
< 0.22.0
Fixed in
0.22.0
CVSS
8.6 High
Weakness
CWE-22
Access needed
No authentication
Advisory published
Credit
rootzero

Investigation summary

The confluence_upload_attachment tool of MCP Atlassian passed its file_path argument to open() with no path check, then uploaded the bytes to Confluence. A caller with access to that tool could upload any file readable by the server process to a writable Confluence destination. An HTTP client needs an HTTP deployment; an agent route additionally depends on the agent following hostile content, which the file-read proof did not test. The earlier fix for GHSA-xjgw-4wvw-rgm4 guarded the download direction only. Fixed in 0.22.0.

The other direction of an attachment

We returned to an earlier MCP Atlassian fix: attachment download now validated the local path before writing to it. We checked the symmetric operation, upload. In attachments.py, its open() calls read the tool’s file_path directly, without the validation used on download paths. The earlier advisory concerned arbitrary write; this time the risk was reading a local file and sending it to Confluence.

We ran the unmodified MCP server through a real stdio client and replaced Confluence with a local HTTP observation endpoint. A tool call with /etc/passwd, then /etc/hostname, put those file contents in the multipart body received by that endpoint. We reported the read on 11 April 2026. Path validation arrived with PR #1448 and version 0.22.0 on 10 July, alongside CVE-2026-77262. An agent influenced by hostile document text is a possible scenario only if its client lets it choose that path and approves the call; our proof concerns the MCP tool itself.

An attachment crosses the host boundary

MCP Atlassian exposes Confluence attachment tools to MCP clients. Download writes a remote attachment to the host; upload reads a host file and sends it to Confluence. We compared the validation on both sides of that boundary.

An MCP server sits in an unusual place. In agent deployments, a model may write tool arguments after reading untrusted text: Jira tickets, Confluence pages, comments. Any argument that touches the host, a path, a URL, a query, has to be treated as attacker input, because a prompt injection hidden in a ticket can steer the agent into calling the tool with values of the attacker’s choosing.

Attachments are the obvious contact point between Confluence and the host filesystem. Downloading writes a remote file to a local path, uploading reads a local path and sends it away. In February 2026, version 0.17.0 fixed an arbitrary file write in the download direction (GHSA-xjgw-4wvw-rgm4, CVE-2026-27825) by introducing a validate_safe_path() helper.

The upload-side open() calls

Reviewing the open( call sites in src/mcp_atlassian/confluence/attachments.py confirmed the asymmetry. Both download paths (lines 223 and 272) call validate_safe_path() before writing. The upload path opens whatever it receives. The tool definition left no doubt about intent: the file_path field documents that it accepts absolute paths and carries no pattern, no validator and no call to the helper.

The issue was then reproduced end to end against the unmodified server at commit d8bc786: a real MCP stdio client (mcp.ClientSession) spawned mcp-atlassian, pointed it at a local HTTP stub standing in for Confluence, and called the tool with /etc/passwd. The stub received the file inside the multipart body, and a second run with /etc/hostname gave the same result.

Root cause

The MCP tool confluence_upload_attachment forwards its argument to ConfluenceFetcher.upload_attachment(). Before 0.22.0, the only processing was a conversion to an absolute path and an existence check:

# src/mcp_atlassian/confluence/attachments.py (before 0.22.0)
try:
    # Convert to absolute path if relative
    if not os.path.isabs(file_path):
        file_path = os.path.abspath(file_path)

    # Check if file exists
    if not os.path.exists(file_path):
        return {"success": False, "error": f"File not found: {file_path}"}

    filename = os.path.basename(file_path)
    attachment = self._upload_attachment_direct(
        content_id, file_path, filename, comment, minor_edit
    )

_upload_attachment_direct() then opens the file and sends it in a multipart request to the configured Confluence:

# attachments.py:477 (commit d8bc786)
files = {"file": (filename, open(file_path, "rb"))}
...
response = self.confluence._session.put(
    url, headers=headers, files=files, data=data
)

On the download side of the same file, the guard is present:

# attachments.py:223 (download path, patched in 0.17.0)
validate_safe_path(target_path)
...
with open(target_path, "wb") as f:

This is CWE-22 (path traversal), in its simplest form: no traversal sequence is even needed, since an absolute path is accepted as is. The @check_write_access decorator on the tool is unrelated to the filesystem; it only refuses the call when READ_ONLY_MODE is enabled, which it is not by default.

Exploitation

The attack is one tool call. Over the HTTP transport, after the usual initialize exchange, it is a JSON-RPC request to the MCP endpoint:

POST /mcp HTTP/1.1
Host: mcp-atlassian.internal:8000
Content-Type: application/json
Accept: application/json, text/event-stream
Mcp-Session-Id: <session id from initialize>

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"confluence_upload_attachment","arguments":{"content_id":"123456","file_path":"/etc/passwd","comment":"poc"}}}

The server applies no check beyond existence: it confirms that /etc/passwd exists, opens it and sends a PUT to <CONFLUENCE_URL>/rest/api/content/123456/child/attachment with the file in the file part. The tool answers with a success message. In the reproduction, the stub captured a 3,339-byte multipart body containing root:x:0:0:root:/root:/bin/bash and the rest of the file.

The bytes end up as an ordinary attachment on a Confluence page. Getting them back does not require controlling Confluence: the same server exposes confluence_download_attachment, which returns an attachment’s content base64-encoded over MCP, and anyone who can read the page in Confluence can download it there. Where the attacker controls the configured Confluence URL, the file arrives directly. The public proof of concept contains the stdio client, the Confluence stub and the run script.

A prompt-injection route is possible if an agent reads attacker-controlled Jira or Confluence content, follows its instruction to invoke the upload tool, and has a writable destination. The public file-read proof does not demonstrate that a particular agent will obey such an instruction.

Impact

Any file readable by the server’s user is exposed: SSH keys, cloud credentials such as ~/.aws/credentials, .env files that may hold the Atlassian API token itself, Kubernetes service account tokens. The tool reports success and the traffic is a normal upload to the expected Confluence host, so nothing looks unusual from the outside.

The published vector, AV:N/PR:N with a scope change, describes an HTTP deployment: when the sse or streamable-http transport is chosen, the server binds 0.0.0.0 by default, and before 0.22.0 an unauthenticated request was served with the operator’s global Atlassian credentials. The write-up and the advisory present streamable-http as the default transport. In the code reviewed at commit d8bc786, the default value of TRANSPORT is stdio, and the HTTP transports are opt-in. For a stdio deployment, the report itself proposed a local vector (AV:L/PR:L, 6.6). A stdio deployment may still be exposed indirectly through an agent that follows attacker-controlled content, but that requires a separate agent-level test; it is not implied by the server’s local transport.

The fix

MCP Atlassian 0.22.0 fixes the issue in PR #1448, merged as commit b041733, a broader security hardening of the project. The abspath() conversion is replaced by the existing helper, in both the Confluence and the Jira upload_attachment():

# src/mcp_atlassian/confluence/attachments.py (0.22.0)
# Confine the upload source to the workspace before it is read: reject
# traversal/absolute paths that escape CWD (arbitrary file read /
# exfiltration via a caller-supplied file_path).
file_path = str(validate_safe_path(file_path))

validate_safe_path() resolves the path, symlinks included, against a base directory that defaults to the current working directory, and raises when the result falls outside it:

# src/mcp_atlassian/utils/io.py
resolved_base = Path(base_dir).resolve(strict=False)
p = Path(path)
if not p.is_absolute():
    p = resolved_base / p
resolved_path = p.resolve(strict=False)

if not resolved_path.is_relative_to(resolved_base):
    raise ValueError(
        f"Path traversal detected: {path} resolves outside {resolved_base}"
    )

The variants are covered: upload_attachments() and the attachments parameter of update_issue go through the same function, and the content_file parameter of the page tools, which read a file and published its content, is confined the same way. The commit message lists GHSA-p6hp-93wp-fh6p among fourteen advisories whose upload half it closes, plus the read half of a fifteenth, so the same sink had been reported several times. The PR also returns 401 on unauthenticated streamable-http requests unless ALLOW_GLOBAL_CRED_FALLBACK is set, and enforces ENABLED_TOOLS at call time, where it previously only filtered the tool list.

One limit remains by design: the boundary is the working directory. A file that lives in that directory, for instance a .env next to the server, can still be uploaded.

Detection and mitigation

  1. Upgrade to MCP Atlassian 0.22.0 or later, and start the server from a dedicated working directory that holds no secrets, since that directory is now the upload boundary.
  2. On versions before 0.22.0, set READ_ONLY_MODE=true if the assistant does not need to write: the upload tool carries @check_write_access and is refused in that mode. Removing it from ENABLED_TOOLS is not enough on those versions, because a tool hidden from the list could still be called by name.
  3. Do not expose the sse or streamable-http transports beyond the machine or the team that uses them: bind to 127.0.0.1 with --host and put an authenticating reverse proxy in front when remote access is required.
  4. Review Confluence attachments uploaded by the service account, looking for names such as passwd, id_rsa, credentials, .env or token, and search the server logs for the line Uploading attachment from followed by a path outside the expected upload directory.
  5. Run the server under a dedicated user or in a container without access to home directories, cloud credentials or mounted service account tokens.

Timeline

Date Event
2026-02-24 MCP Atlassian 0.17.0 released with the download-side fix for GHSA-xjgw-4wvw-rgm4
2026-04-11 Upload-side issue found and reported privately through a GitHub security advisory
2026-07-10 PR #1448 merged (b041733) and MCP Atlassian 0.22.0 released
2026-07-10 Advisory GHSA-p6hp-93wp-fh6p published, CVE-2026-77262 assigned
2026-09-22 Advisory added to the GitHub Advisory Database

References

  1. Vendor advisory · CVE-2026-77262 github.com/sooperset/mcp-atlassian/security/advisories/GHSA-p6hp-93wp-fh6p
  2. NVD entry · CVE-2026-77262 nvd.nist.gov/vuln/detail/CVE-2026-77262
  3. CVE.org record · CVE-2026-77262 www.cve.org/CVERecord?id=CVE-2026-77262
  4. MITRE CWE-22 cwe.mitre.org/data/definitions/22.html
  5. Project repository github.com/sooperset/mcp-atlassian
  6. Proof of concept and reproduction notes github.com/romain-deperne/CVE-2026-77262

Cite this advisory

rootzero. “CVE-2026-77262: arbitrary file read in MCP Atlassian through confluence_upload_attachment”. Vulnerability research, rootzero, 10 July 2026. https://rootzero.eu/en/research/cve/cve-2026-77262-mcp-atlassian-arbitrary-file-read/

#Vulnerability research

Have your code audited before someone else does.

Your team needs to decide what to fix before a release or assess a critical dependency. We scope an engagement around that decision, with evidence and explained remediation priorities.