mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
fix(research): gate /api/research/spinoff on session ownership (#878)
The spinoff endpoint authenticated the caller (_require_user) but never verified the research session belonged to them before reading the persisted report and seeding it into a new chat session owned by the caller. Any authenticated user who knew or guessed another user's research session ID could exfiltrate that user's full report into their own session — a cross-user data disclosure (IDOR). Every other endpoint in this router gates on _owns_in_memory / _assert_owns_research right after validating the session ID; spinoff was the lone exception. Add the same _owns_in_memory check (covers both the in-memory task and the on-disk JSON) so a non-owner gets a 404 before any data is read or a session is created. Add regression tests pinning the anonymous (401) and wrong-owner (404) cases.
This commit is contained in:
@@ -177,6 +177,35 @@ def test_research_delete_rejects_anonymous():
|
||||
assert exc.value.status_code == 401
|
||||
|
||||
|
||||
def test_research_spinoff_rejects_anonymous():
|
||||
"""spinoff must 401 before reading any research data."""
|
||||
from routes.research_routes import setup_research_routes
|
||||
rh = MagicMock()
|
||||
router = setup_research_routes(rh, session_manager=MagicMock())
|
||||
target = next(r.endpoint for r in router.routes if getattr(r, "path", "") == "/api/research/spinoff/{session_id}")
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
asyncio.run(target(session_id="x", request=_fake_request(user=None)))
|
||||
assert exc.value.status_code == 401
|
||||
|
||||
|
||||
def test_research_spinoff_rejects_wrong_owner():
|
||||
"""A user must not be able to spin off (and thereby read) another user's
|
||||
research report. The ownership gate must 404 before any data is read or a
|
||||
new session is created. Regression for the cross-user disclosure IDOR."""
|
||||
from routes.research_routes import setup_research_routes
|
||||
sm = MagicMock()
|
||||
rh = MagicMock()
|
||||
rh._active_tasks = {"x": {"owner": "alice"}}
|
||||
rh.get_result.return_value = "TOP SECRET REPORT"
|
||||
router = setup_research_routes(rh, session_manager=sm)
|
||||
target = next(r.endpoint for r in router.routes if getattr(r, "path", "") == "/api/research/spinoff/{session_id}")
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
asyncio.run(target(session_id="x", request=_fake_request(user="bob")))
|
||||
assert exc.value.status_code == 404
|
||||
# The attacker must never get a session created on their behalf.
|
||||
sm.create_session.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pop_notifications owner filter
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user