fix: skip Authorization header when federation key is empty

Empty remote_instances[].key produced 'Bearer ' (illegal header value)
causing httpx to reject the request entirely. Fix: only include the
Authorization header when the key is non-empty. Remotes without keys
still work if their auth allows it (e.g., localhost bypass).

Fixes all 5 sites: fetch_remote GET, connect POST, bell/clear POST,
create-session POST, and WebSocket proxy additional_headers.
This commit is contained in:
Brian Krabach
2026-04-06 05:25:18 -07:00
parent 2c21dfeb3e
commit 39f076502f
2 changed files with 61 additions and 5 deletions
+7 -5
View File
@@ -760,7 +760,9 @@ async def federation_terminal_ws_proxy(websocket: WebSocket, remote_id: int) ->
async with websockets.connect(
ws_url,
subprotocols=[Subprotocol("tty")],
additional_headers={"Authorization": f"Bearer {remote_key}"},
additional_headers={"Authorization": f"Bearer {remote_key}"}
if remote_key
else {},
) as remote_ws:
async def client_to_remote() -> None:
@@ -926,7 +928,7 @@ async def federation_sessions(request: Request) -> list[dict]:
try:
resp = await http_client.get(
f"{url.rstrip('/')}/api/sessions",
headers={"Authorization": f"Bearer {key}"},
headers={"Authorization": f"Bearer {key}"} if key else {},
)
if resp.status_code in (401, 403):
return [
@@ -1026,7 +1028,7 @@ async def federation_connect(
try:
resp = await http_client.post(
url,
headers={"Authorization": f"Bearer {remote_key}"},
headers={"Authorization": f"Bearer {remote_key}"} if remote_key else {},
)
resp.raise_for_status()
return resp.json()
@@ -1072,7 +1074,7 @@ async def federation_bell_clear(
try:
resp = await http_client.post(
url,
headers={"Authorization": f"Bearer {remote_key}"},
headers={"Authorization": f"Bearer {remote_key}"} if remote_key else {},
)
resp.raise_for_status()
return resp.json()
@@ -1119,7 +1121,7 @@ async def federation_create_session(
try:
resp = await http_client.post(
url,
headers={"Authorization": f"Bearer {remote_key}"},
headers={"Authorization": f"Bearer {remote_key}"} if remote_key else {},
json={"name": payload.name},
)
resp.raise_for_status()
+54
View File
@@ -2624,3 +2624,57 @@ def test_federation_create_session_returns_502_when_remote_returns_error(
response = client.post("/api/federation/0/sessions", json={"name": "new-session"})
assert response.status_code == 502
# ---------------------------------------------------------------------------
# Federation Authorization header safety — guard against empty key
# ---------------------------------------------------------------------------
def test_federation_auth_headers_guard_empty_key():
"""Every federation Authorization header construction must guard against empty key.
An empty remote_instances[].key produces 'Bearer ' (trailing space, empty
token). httpx rejects that with "Illegal header value b'Bearer '" which
silently makes the remote appear unreachable.
The fix: every site that constructs the header must use the pattern
headers={"Authorization": f"Bearer {key}"} if key else {}
or an equivalent conditional, so an empty key simply omits the header.
This is a source-inspection test — it catches regressions without spinning
up a live server.
"""
import inspect
import muxplex.main as main_mod
source = inspect.getsource(main_mod)
# Collect every line that constructs an Authorization Bearer header (not
# comment or docstring lines — we only care about executable code lines).
offending: list[str] = []
for line in source.splitlines():
stripped = line.strip()
# Skip comment and docstring lines
if (
stripped.startswith("#")
or stripped.startswith('"""')
or stripped.startswith("'\"'\"'")
):
continue
# Match lines that build the header dict value
if (
'"Authorization"' in stripped
and "Bearer" in stripped
and 'f"Bearer' in stripped
):
# Must have an inline `if` guard — e.g. `{...} if key else {}`
if " if " not in stripped:
offending.append(stripped)
assert not offending, (
"Unguarded federation Bearer header(s) found in main.py — "
"use `{...} if key else {}` to skip the header when key is empty:\n"
+ "\n".join(f" {line}" for line in offending)
)