_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.
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:
# 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:
302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/Tested on commit d8bc786 (v0.21.1). No real credentials needed.
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:
$ 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)
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.
{
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-22T20:35:13Z",
"nvd_published_at": "2026-09-22T18:17:18Z",
"severity": "HIGH"
}