From fa2f2eb5d1ce192bd93df23065ef332bb5041ca1 Mon Sep 17 00:00:00 2001 From: RaresKeY <158580472+RaresKeY@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:02:43 +0000 Subject: [PATCH 1/2] fix(companion): preserve models with auth disabled --- companion/routes.py | 14 +++++++---- tests/test_companion_readonly.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/companion/routes.py b/companion/routes.py index 0191640ef..634d5c045 100644 --- a/companion/routes.py +++ b/companion/routes.py @@ -23,7 +23,7 @@ from fastapi import APIRouter, HTTPException, Request from fastapi.responses import HTMLResponse from core.middleware import require_admin -from src.auth_helpers import get_current_user +from src.auth_helpers import _auth_disabled, get_current_user from companion import pairing as _pairing @@ -113,8 +113,9 @@ def setup_companion_routes() -> APIRouter: The stock /api/models route scopes to get_current_user, which for a bearer token is the sandboxed pseudo-user "api" (owns nothing). Here we scope to the token's real owner instead, plus legacy null-owner shared - rows -- the same rule as owner_filter. Read-only; never returns api_key - material. + rows -- the same rule as owner_filter. Explicit auth-disabled mode keeps + the stock route's single-user all-endpoints view. Read-only; never + returns api_key material. """ require_models_scope(request) import json as _json @@ -123,6 +124,11 @@ def setup_companion_routes() -> APIRouter: from src.endpoint_resolver import build_chat_url owner = token_owner(request) + single_user_mode = ( + owner is None + and not getattr(request.state, "api_token", False) + and _auth_disabled() + ) out = [] db = SessionLocal() try: @@ -133,7 +139,7 @@ def setup_companion_routes() -> APIRouter: if owner: q = q.filter((ModelEndpoint.owner == owner) | (ModelEndpoint.owner == None)) # noqa: E711 for ep in q.all(): - if not owner_can_see(ep.owner, owner): + if not single_user_mode and not owner_can_see(ep.owner, owner): continue try: model_ids = _json.loads(ep.cached_models) if ep.cached_models else [] diff --git a/tests/test_companion_readonly.py b/tests/test_companion_readonly.py index 589621b66..39f18599d 100644 --- a/tests/test_companion_readonly.py +++ b/tests/test_companion_readonly.py @@ -257,6 +257,7 @@ def test_models_route_rejects_api_token_without_chat_scope(monkeypatch): def test_models_route_unresolved_owner_returns_only_shared_rows(monkeypatch): + monkeypatch.setenv("AUTH_ENABLED", "true") rows = [ _ep(1, "alice-endpoint", "alice"), _ep(2, "shared-endpoint", None), @@ -278,6 +279,46 @@ def test_models_route_unresolved_owner_returns_only_shared_rows(monkeypatch): assert _endpoint_names(endpoints) == ["shared-endpoint"] +def test_models_route_auth_enabled_anonymous_returns_only_shared_rows(monkeypatch): + monkeypatch.setenv("AUTH_ENABLED", "true") + rows = [ + _ep(1, "alice-endpoint", "alice"), + _ep(2, "shared-endpoint", None), + _ep(3, "bob-endpoint", "bob"), + ] + monkeypatch.setattr(companion_routes, "get_current_user", lambda request: None) + + endpoints = _call_models_route( + monkeypatch, + rows, + _request(api_token=False, current_user=None), + ) + + assert _endpoint_names(endpoints) == ["shared-endpoint"] + + +def test_models_route_auth_disabled_returns_all_enabled_rows(monkeypatch): + monkeypatch.setenv("AUTH_ENABLED", "false") + rows = [ + _ep(1, "alice-endpoint", "alice"), + _ep(2, "shared-endpoint", None), + _ep(3, "bob-endpoint", "bob"), + ] + monkeypatch.setattr(companion_routes, "get_current_user", lambda request: None) + + endpoints = _call_models_route( + monkeypatch, + rows, + _request(api_token=False, current_user=None), + ) + + assert _endpoint_names(endpoints) == [ + "alice-endpoint", + "shared-endpoint", + "bob-endpoint", + ] + + def test_models_route_filters_hidden_models_and_secret_fields(monkeypatch): rows = [ _ep( From f0bea2b173fbfb92cfb9f74b18788088f67c4358 Mon Sep 17 00:00:00 2001 From: RaresKeY <158580472+RaresKeY@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:23:32 +0000 Subject: [PATCH 2/2] test(companion): guard auth-disabled model scoping --- tests/test_companion_readonly.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test_companion_readonly.py b/tests/test_companion_readonly.py index 39f18599d..9ea0d63f0 100644 --- a/tests/test_companion_readonly.py +++ b/tests/test_companion_readonly.py @@ -279,6 +279,47 @@ def test_models_route_unresolved_owner_returns_only_shared_rows(monkeypatch): assert _endpoint_names(endpoints) == ["shared-endpoint"] +def test_models_route_auth_disabled_does_not_widen_ownerless_api_token(monkeypatch): + monkeypatch.setenv("AUTH_ENABLED", "false") + rows = [ + _ep(1, "alice-endpoint", "alice"), + _ep(2, "shared-endpoint", None), + _ep(3, "bob-endpoint", "bob"), + ] + monkeypatch.setattr(companion_routes, "get_current_user", lambda request: None) + + endpoints = _call_models_route( + monkeypatch, + rows, + _request( + api_token=True, + api_token_owner=None, + api_token_scopes=["chat"], + current_user="api", + ), + ) + + assert _endpoint_names(endpoints) == ["shared-endpoint"] + + +def test_models_route_auth_disabled_keeps_cookie_owner_scoped(monkeypatch): + monkeypatch.setenv("AUTH_ENABLED", "false") + rows = [ + _ep(1, "alice-endpoint", "alice"), + _ep(2, "shared-endpoint", None), + _ep(3, "bob-endpoint", "bob"), + ] + monkeypatch.setattr(companion_routes, "get_current_user", lambda request: "alice") + + endpoints = _call_models_route( + monkeypatch, + rows, + _request(api_token=False, current_user="alice"), + ) + + assert _endpoint_names(endpoints) == ["alice-endpoint", "shared-endpoint"] + + def test_models_route_auth_enabled_anonymous_returns_only_shared_rows(monkeypatch): monkeypatch.setenv("AUTH_ENABLED", "true") rows = [