fix: pin Web Push connections to validated addresses (Closes #1096)
CI / lint (pull_request) Successful in 2m52s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 2m57s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-18 21:26:32 +00:00
1 parent a38934b099
commit ad40cb5fb9
3 files changed
+168 -13

No files matched your search

+75 -1
View File
@@ -7,9 +7,14 @@ from types import SimpleNamespace
import httpx
import pytest
import requests
from src import dashboard_auth, gitea_proxy, main
from src.push_notifications import PushConfiguration, dispatch_unread_updates
from src.push_notifications import (
PushConfiguration,
dispatch_unread_updates,
send_web_push,
)
from src.push_subscription_store import PushSubscriptionStore
from src.push_endpoint_policy import UnsafePushEndpoint
@@ -692,6 +697,75 @@ async def test_dispatch_removes_an_endpoint_that_rebinds_private_without_contact
assert store.is_subscribed("unsafe-device") is False
@pytest.mark.anyio
async def test_web_push_transport_dials_the_address_validated_for_the_original_tls_host():
endpoint = "https://push.example/device-a"
resolved = SimpleNamespace(
endpoint=endpoint,
hostname="push.example",
port=443,
addresses=("93.184.216.34",),
)
resolutions = []
connection = {}
async def resolve_once(value):
resolutions.append(value)
return resolved
def inspect_transport(**kwargs):
session = kwargs["requests_session"]
assert session.trust_env is False
request = session.prepare_request(requests.Request("POST", endpoint))
adapter = session.get_adapter(endpoint)
adapter.add_headers(request)
host, tls = adapter.build_connection_pool_key_attributes(request, True)
connection.update(host=host, tls=tls, headers=dict(request.headers))
await send_web_push(
{"endpoint": endpoint, "keys": {"p256dh": "key", "auth": "secret"}},
"{}",
PushConfiguration("public", "private", "mailto:ops@example.com"),
endpoint_resolver=resolve_once,
webpush_sender=inspect_transport,
)
assert resolutions == [endpoint]
assert connection["host"] == {
"scheme": "https",
"host": "93.184.216.34",
"port": 443,
}
assert connection["tls"]["assert_hostname"] == "push.example"
assert connection["tls"]["server_hostname"] == "push.example"
assert connection["headers"]["Host"] == "push.example"
@pytest.mark.anyio
async def test_connect_time_rebinding_rejection_removes_only_the_unsafe_device(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
for session_id in ("unsafe-device", "healthy-device"):
store.upsert(session_id, {
"endpoint": f"https://push.example/{session_id}",
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
})
sent = []
async def unread():
return {"items": [{"id": 8}]}
async def send(subscription, _payload):
if subscription["endpoint"].endswith("unsafe-device"):
raise UnsafePushEndpoint("Endpoint rebound before connection")
sent.append(subscription["endpoint"])
config = PushConfiguration("public", "private", "mailto:ops@example.com")
assert await dispatch_unread_updates(store, config, unread, send) == 1
assert store.is_subscribed("unsafe-device") is False
assert store.is_subscribed("healthy-device") is True
assert sent == ["https://push.example/healthy-device"]
@pytest.mark.anyio
async def test_transient_endpoint_failure_does_not_block_healthy_devices(tmp_path):
store = PushSubscriptionStore(tmp_path / "push.sqlite3")