You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/10/06 14:14:24 UTC

(tooling-trusted-releases) branch sbp updated: Disallow PubSub redirects to prevent downgrade to HTTP

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 3adc33f1 Disallow PubSub redirects to prevent downgrade to HTTP
3adc33f1 is described below

commit 3adc33f18ccad0f1acb047878eb38c83d8638dbc
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Tue Oct 6 15:13:30 2026 +0100

    Disallow PubSub redirects to prevent downgrade to HTTP
---
 atr/pubsub.py             |  4 +++-
 tests/unit/test_pubsub.py | 13 +++++++++++++
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/atr/pubsub.py b/atr/pubsub.py
index 509c93b5..92a66cc6 100644
--- a/atr/pubsub.py
+++ b/atr/pubsub.py
@@ -177,9 +177,11 @@ async def _handle_payload(payload: dict[str, Any]) -> str | None:
 async def _process_connection(session, pubsub_url, since):
     # Connect to pubsub and listen for payloads.
     headers = {"X-Fetch-Since-Cursor": since} if since else {}
-    async with session.get(pubsub_url, headers=headers) as conn:
+    async with session.get(pubsub_url, headers=headers, allow_redirects=False) as conn:
         # print('LIMITS:', conn.content.get_read_buffer_limits())
         conn.raise_for_status()
+        if 300 <= conn.status < 400:
+            raise ValueError(f"PubSub redirects are not allowed (HTTP {conn.status})")
         if conn.content_type != _STREAM_CONTENT_TYPE:
             log.warning(f"Unexpected pubsub content type: {conn.content_type}")
         if since:
diff --git a/tests/unit/test_pubsub.py b/tests/unit/test_pubsub.py
index b9680ff8..e25fcc56 100644
--- a/tests/unit/test_pubsub.py
+++ b/tests/unit/test_pubsub.py
@@ -20,6 +20,7 @@ import json
 import pathlib
 from typing import Any
 
+import aiohttp
 import aiohttp.web as web
 import pytest
 
@@ -33,6 +34,7 @@ class Server:
     def __init__(self) -> None:
         self.connections = 0
         self.cursors: list[str | None] = []
+        self.redirect = 0
         self.reject = 0
         self.scripts: list[list[bytes]] = []
         self.url = ""
@@ -40,6 +42,8 @@ class Server:
     async def handle(self, request: web.Request) -> web.StreamResponse:
         self.connections += 1
         self.cursors.append(request.headers.get("X-Fetch-Since-Cursor"))
+        if self.redirect:
+            return web.Response(status=self.redirect, headers={"Location": self.url}, body=_line(_EVENT))
         if self.reject > 0:
             self.reject -= 1
             return web.Response(status=503)
@@ -125,6 +129,15 @@ async def test_listen_yields_events_and_keepalives(server: Server) -> None:
     assert server.connections == 1
 
 
+@pytest.mark.parametrize("status", [301, 302, 303, 307, 308])
+async def test_process_connection_rejects_redirects(server: Server, status: int) -> None:
+    server.redirect = status
+    async with aiohttp.ClientSession(auth=aiohttp.BasicAuth("test", "test")) as session:
+        with pytest.raises(ValueError, match="PubSub redirects are not allowed"):
+            await anext(pubsub._process_connection(session, server.url, None))
+    assert server.connections == 1
+
+
 async def test_start_checkpoints_at_keepalives(monkeypatch: pytest.MonkeyPatch) -> None:
     saves: list[tuple[str, str]] = []
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org