CVE-2026-77261
highpriority · track · 34EPSS 0.004 · 31 %ileUSEPSS · scored 2026-10-27
MCP Atlassian is a Model Context Protocol (MCP) server for Atlassian products (Confluence and Jira). Prior to 0.22.0, _make_ssrf_safe_hook is omitted from JiraFetcher and ConfluenceFetcher sessions created through the basic-auth and oauth_pat branches. If an attacker-controlled or compromised configured Atlassian instance returns a redirect to an internal address, those sessions can follow the redirect without revalidating its destination. This issue is fixed in version 0.22.0.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (2)
Products and version ranges known to be affected.
advisories (2)
Vendor and ecosystem advisories describing this.
- GHSA-6529-c226-h328MCP Atlassian: SSRF redirect protection missing for basic-auth and OAuth authentication branches2026-09-22ghsa · osvUS
### Summary `_make_ssrf_safe_hook()` blocks HTTP redirects to private/internal IPs by validating the `Location` header before the client follows a `3xx` response. The problem is that this hook is only attached in one of three authentication branches — the header-PAT path. Basic auth and OAuth branches skip it entirely, so if the connected Atlassian server returns a redirect to something like `http://169.254.169.254/`, the `requests` session follows it without complaint. This is an incomplete fix for GHSA-7r34-79r5-rcc9. The hook works fine when it's there — it just isn't there for most production auth configurations. ### Details In `src/mcp_atlassian/servers/dependencies.py`, three branches construct a fetcher and call `_create_and_validate()`. Only Branch 1 passes `attach_ssrf_hook=True`: ```python # Branch 1 (header PAT) — hook attached return _create_and_validate(request, spec, header_config, "header_pat", attach_ssrf_hook=True) # Branch 2 (basic auth) — hook missing return _create_and_validate(request, spec, user_config, "basic", user_email=user_email) # Branch 3 (OAuth/PAT) — hook missing return _create_and_validate(request, spec, user_config, "oauth_pat", user_email=user_email) ``` `attach_ssrf_hook` defaults to `False`, so branches 2 and 3 silently skip the protection. The hook itself (`_make_ssrf_safe_hook`) is straightforward — it checks `response.is_redirect`, grabs the `Location` header, and calls `validate_url_for_ssrf()` to reject private IPs. It works correctly when present. Typical attack flow: 1. Attacker controls or compromises an Atlassian instance (Cloud or Server) 2. MCP server connects using basic auth or OAuth credentials (most production setups) 3. Atlassian returns `302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/` 4. The unprotected session follows the redirect 5. AWS IAM credentials (or other internal service data) are returned to the attacker ### PoC Tested on commit `d8bc786` (v0.21.1). No real credentials needed. ```python from unittest.mock import MagicMock import requests from mcp_atlassian.servers.dependencies import _make_ssrf_safe_hook from mcp_atlassian.utils.urls import validate_url_for_ssrf from mcp_atlassian.jira import JiraFetcher from mcp_atlassian.jira.config import JiraConfig config = JiraConfig( url="https://attacker.atlassian.net", auth_type="basic", username="victim@example.com", api_token="victim-token", ) fetcher = JiraFetcher(config=config) session = fetcher.jira._session hooks = session.hooks.get("response", []) print("hooks on basic-auth session:", [h.__name__ for h in hooks] or "none") fake_redirect = MagicMock(spec=requests.Response) fake_redirect.is_redirect = True fake_redirect.headers = {"Location": "http://169.254.169.254/latest/meta-data/"} blocked = False for h in hooks: try: h(fake_redirect) except ValueError as e: blocked = True print("blocked:", e) if not blocked: print("redirect to 169.254.169.254 not blocked on basic-auth session") # show header-PAT branch does block it hook = _make_ssrf_safe_hook(validate_url_for_ssrf) try: hook(fake_redirect) except ValueError as e: print("header-PAT branch blocks:", e) ``` Output: <img width="2490" height="214" alt="image" src="https://github.com/user-attachments/assets/c7d9d7e2-4c37-4abd-95a3-4ddd9f6bd735" /> ``` $ uv run python3 /tmp/test.py hooks on basic-auth session: none redirect to 169.254.169.254 not blocked on basic-auth session header-PAT branch blocks: Redirect blocked (SSRF): Blocked IP address: 169.254.169.254 (non-global) ``` ### Impact Basic auth and OAuth cover most production Atlassian Cloud deployments, so this affects the majority of HTTP-mode multi-user setups. An attacker with control over the Atlassian server can redirect MCP server requests to internal infrastructure — cloud metadata endpoints, internal Kubernetes API, databases, or any service reachable from the MCP server's network. The fix is one line per affected branch: pass `attach_ssrf_hook=True` to `_create_and_validate()` in branches 2 and 3, the same way branch 1 already does.
- PYSEC-2026-4085MCP Atlassian: SSRF redirect protection missing for basic-auth and OAuth authentication branches2026-10-01vendor · osvUS
### Summary `_make_ssrf_safe_hook()` blocks HTTP redirects to private/internal IPs by validating the `Location` header before the client follows a `3xx` response. The problem is that this hook is only attached in one of three authentication branches — the header-PAT path. Basic auth and OAuth branches skip it entirely, so if the connected Atlassian server returns a redirect to something like `http://169.254.169.254/`, the `requests` session follows it without complaint. This is an incomplete fix for GHSA-7r34-79r5-rcc9. The hook works fine when it's there — it just isn't there for most production auth configurations. ### Details In `src/mcp_atlassian/servers/dependencies.py`, three branches construct a fetcher and call `_create_and_validate()`. Only Branch 1 passes `attach_ssrf_hook=True`: ```python # Branch 1 (header PAT) — hook attached return _create_and_validate(request, spec, header_config, "header_pat", attach_ssrf_hook=True) # Branch 2 (basic auth) — hook missing return _create_and_validate(request, spec, user_config, "basic", user_email=user_email) # Branch 3 (OAuth/PAT) — hook missing return _create_and_validate(request, spec, user_config, "oauth_pat", user_email=user_email) ``` `attach_ssrf_hook` defaults to `False`, so branches 2 and 3 silently skip the protection. The hook itself (`_make_ssrf_safe_hook`) is straightforward — it checks `response.is_redirect`, grabs the `Location` header, and calls `validate_url_for_ssrf()` to reject private IPs. It works correctly when present. Typical attack flow: 1. Attacker controls or compromises an Atlassian instance (Cloud or Server) 2. MCP server connects using basic auth or OAuth credentials (most production setups) 3. Atlassian returns `302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/` 4. The unprotected session follows the redirect 5. AWS IAM credentials (or other internal service data) are returned to the attacker ### PoC Tested on commit `d8bc786` (v0.21.1). No real credentials needed. ```python from unittest.mock import MagicMock import requests from mcp_atlassian.servers.dependencies import _make_ssrf_safe_hook from mcp_atlassian.utils.urls import validate_url_for_ssrf from mcp_atlassian.jira import JiraFetcher from mcp_atlassian.jira.config import JiraConfig config = JiraConfig( url="https://attacker.atlassian.net", auth_type="basic", username="victim@example.com", api_token="victim-token", ) fetcher = JiraFetcher(config=config) session = fetcher.jira._session hooks = session.hooks.get("response", []) print("hooks on basic-auth session:", [h.__name__ for h in hooks] or "none") fake_redirect = MagicMock(spec=requests.Response) fake_redirect.is_redirect = True fake_redirect.headers = {"Location": "http://169.254.169.254/latest/meta-data/"} blocked = False for h in hooks: try: h(fake_redirect) except ValueError as e: blocked = True print("blocked:", e) if not blocked: print("redirect to 169.254.169.254 not blocked on basic-auth session") # show header-PAT branch does block it hook = _make_ssrf_safe_hook(validate_url_for_ssrf) try: hook(fake_redirect) except ValueError as e: print("header-PAT branch blocks:", e) ``` Output: <img width="2490" height="214" alt="image" src="https://github.com/user-attachments/assets/c7d9d7e2-4c37-4abd-95a3-4ddd9f6bd735" /> ``` $ uv run python3 /tmp/test.py hooks on basic-auth session: none redirect to 169.254.169.254 not blocked on basic-auth session header-PAT branch blocks: Redirect blocked (SSRF): Blocked IP address: 169.254.169.254 (non-global) ``` ### Impact Basic auth and OAuth cover most production Atlassian Cloud deployments, so this affects the majority of HTTP-mode multi-user setups. An attacker with control over the Atlassian server can redirect MCP server requests to internal infrastructure — cloud metadata endpoints, internal Kubernetes API, databases, or any service reachable from the MCP server's network. The fix is one line per affected branch: pass `attach_ssrf_hook=True` to `_create_and_validate()` in branches 2 and 3, the same way branch 1 already does.
also known as (2)
Other names and identifiers for this entity.
sources
Which sources assert this record, and when.
references (4) nvd
External citations — patches, advisories, analysis.
- https://github.com/sooperset/mcp-atlassian/commit/b041733473f95119dd539542a43c280737a8e460Patch
- https://github.com/sooperset/mcp-atlassian/pull/1448Patch
- https://github.com/sooperset/mcp-atlassian/releases/tag/v0.22.0Release Notes
- https://github.com/sooperset/mcp-atlassian/security/advisories/GHSA-6529-c226-h328ExploitVendor Advisory
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan