mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
fix: stop leaking upstream auth-error bodies into user comments
get_github_token_for_user folded the raw upstream response text into the error string that becomes a Slack/Linear comment (AUTH-RESP-LEAK-01). Log the full body server-side only and return a generic "GitHub auth failed (status <code>)". Also document the accepted shared-installation-token blast radius on the bot-token-only path (AUTHZ-003).
This commit is contained in:
parent
3e56062c43
commit
ba9865e1cf
2 changed files with 65 additions and 2 deletions
|
|
@ -195,8 +195,10 @@ async def get_github_token_for_user(ls_user_id: str, tenant_id: str) -> dict[str
|
|||
return {"error": f"Unexpected auth result: {response_data}"}
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
# Log the full upstream body server-side only; the returned error becomes a
|
||||
# user-facing Slack/Linear comment, so never echo the raw response text.
|
||||
logger.error("GitHub auth API HTTP error: %s - %s", e.response.status_code, e.response.text)
|
||||
return {"error": f"HTTP error: {e.response.status_code} - {e.response.text}"}
|
||||
return {"error": f"GitHub auth failed (status {e.response.status_code})"}
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error("GitHub auth API call failed: %s: %s", type(e).__name__, str(e))
|
||||
return {"error": str(e)}
|
||||
|
|
@ -383,7 +385,13 @@ async def _resolve_dashboard_user_token(
|
|||
|
||||
|
||||
async def _resolve_bot_installation_token(thread_id: str) -> tuple[str, str | None]:
|
||||
"""Get a GitHub App installation token and cache it for the thread."""
|
||||
"""Get a GitHub App installation token and cache it for the thread.
|
||||
|
||||
AUTHZ-003 (accepted): in bot-token-only mode every run shares one GitHub App
|
||||
installation token, so its blast radius is the whole installation rather than
|
||||
a single user. This is a documented, accepted prod posture for this
|
||||
single-tenant deployment, not a defect.
|
||||
"""
|
||||
bot_token, expires_at = await get_github_app_installation_token_with_expiry()
|
||||
if not bot_token:
|
||||
raise RuntimeError(
|
||||
|
|
|
|||
55
tests/test_auth_error_leak.py
Normal file
55
tests/test_auth_error_leak.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"""AUTH-RESP-LEAK-01: upstream auth-error bodies must not reach user-facing text."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from agent.utils import auth
|
||||
|
||||
_SECRET_BODY = "SENSITIVE-UPSTREAM-BODY-9999"
|
||||
|
||||
|
||||
class _Resp:
|
||||
def __init__(self, status_code: int, text: str) -> None:
|
||||
self.status_code = status_code
|
||||
self.text = text
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
raise httpx.HTTPStatusError(
|
||||
"boom",
|
||||
request=httpx.Request("POST", "http://example.test"),
|
||||
response=self, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def json(self) -> Any:
|
||||
return {}
|
||||
|
||||
|
||||
class _Client:
|
||||
def __init__(self, resp: _Resp) -> None:
|
||||
self._resp = resp
|
||||
|
||||
async def __aenter__(self) -> _Client:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_a: Any) -> None:
|
||||
return None
|
||||
|
||||
async def post(self, *_a: Any, **_k: Any) -> _Resp:
|
||||
return self._resp
|
||||
|
||||
|
||||
def test_http_error_returns_generic_message(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(auth, "GITHUB_OAUTH_PROVIDER_ID", "provider-id")
|
||||
monkeypatch.setattr(auth, "X_SERVICE_AUTH_JWT_SECRET", "jwt-secret")
|
||||
monkeypatch.setattr(httpx, "AsyncClient", lambda *a, **k: _Client(_Resp(500, _SECRET_BODY)))
|
||||
|
||||
result = asyncio.run(auth.get_github_token_for_user("ls-user", "tenant"))
|
||||
|
||||
assert "error" in result
|
||||
assert _SECRET_BODY not in result["error"]
|
||||
assert "500" in result["error"]
|
||||
Loading…
Add table
Reference in a new issue