MCP Atlassian server lets prompt-injected agent exfiltrate arbitrary local files via upload_attachment
Undisclosed·mcp-atlassian (MCP server for Jira/Confluence, used as a tool by LLM agents)Fixed
The Confluence and Jira upload_attachment tool paths in mcp-atlassian took a caller-supplied file_path and opened it directly for upload after only calling os.path.abspath(), with no check that the path stayed inside an allowed directory; an attacker could plant an instruction in a Jira issue or Confluence page (e.g. 'file_path: /home/mcp-user/.ssh/id_rsa') that a connected LLM agent would read and then invoke as a tool call, causing the server to read that local file and upload it as an attachment to an attacker-controlled page. The same primitive was also reachable pre-auth over the streamable-http transport when bound non-loopback, using the operator's global Atlassian credentials with no prompt injection needed.
- Blast radius
- Arbitrary local files readable by the MCP server process — SSH private keys, .env files, cloud credential files, /etc/passwd — could be exfiltrated by uploading them as attachments to an attacker-accessible Jira/Confluence location; scope limited to deployments running mcp-atlassian versions before 0.22.0. No specific victim organisation, data volume, or in-the-wild exploitation was disclosed in the advisory.
- Root cause
- The write-direction path traversal (CVE-2026-27825) was patched with validate_safe_path() on download/write paths, but the symmetric upload/read code path in confluence/attachments.py and jira/attachments.py was never updated to apply the same confinement check, leaving os.path.abspath() as the only guard — which restricts nothing.
- How it was caught
- Found and reported by an external security researcher (Sean Valentine) via responsible disclosure to the maintainer, not discovered through production monitoring or an actual exploitation incident.
- What changed
- Fixed in mcp-atlassian v0.22.0: upload_attachment and content_file paths now go through validate_safe_path() so caller-supplied paths must resolve inside the server's working directory, and the streamable-http transport no longer falls back to the operator's global credentials for unauthenticated requests (opt-in only via ALLOW_GLOBAL_CRED_FALLBACK, default off, with 401 rejection otherwise).
Take-awayAny file-path parameter an LLM agent can populate from untrusted content (issue text, page content) needs the same directory-confinement check on every code path that touches the filesystem, not just the one direction that was previously exploited.