mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-05 20:52:22 +01:00
feat(security): apply audited PR #6738 remediations and test hardening
- Enforce vision probe SSRF safe local host validation, SafeProbeBackend TOCTOU elimination, and session owner authorization - Authorize session titling endpoints against registered owner configuration - Sanitize sensitive metadata logging from agent loop debug logs - Add async non-blocking execution for spec router tools and thinking decisions - Harden email cache invalidation fencing and thread-safe mutation - Add regression test suites for legacy browser authority, spec contracts, spec router request scoping, and task MCP delivery
This commit is contained in:
+79
-21
@@ -583,15 +583,25 @@ async def auto_name_session(session_manager, sess):
|
||||
except Exception:
|
||||
_is_spec_model = False
|
||||
if _is_spec_model and endpoint_url:
|
||||
_headers = getattr(sess, "headers", None)
|
||||
if isinstance(_headers, str):
|
||||
try:
|
||||
_headers = json.loads(_headers)
|
||||
except Exception:
|
||||
_headers = None
|
||||
try:
|
||||
from src.database import SessionLocal
|
||||
from src.endpoint_resolver import (build_chat_url, build_headers,
|
||||
resolve_endpoint_runtime, resolve_owner_registered_endpoint)
|
||||
from src.owner_identity import auth_disabled
|
||||
|
||||
owner = getattr(sess, "owner", None)
|
||||
if not owner and not auth_disabled():
|
||||
raise ValueError("Session has no owner")
|
||||
db = SessionLocal()
|
||||
try:
|
||||
endpoint = resolve_owner_registered_endpoint(db, endpoint_url, owner)
|
||||
base, key = resolve_endpoint_runtime(endpoint, owner=owner)
|
||||
title_url = build_chat_url(base)
|
||||
title_headers = build_headers(key, base)
|
||||
finally:
|
||||
db.close()
|
||||
title = await llm_call_async(
|
||||
endpoint_url,
|
||||
title_url,
|
||||
model_name,
|
||||
[
|
||||
{"role": "system", "content": "Generate a short title (3-6 words, no quotes) for a conversation that starts with this message. Reply with ONLY the title, nothing else. Do NOT include any thinking, reasoning, or explanation \u2014 just the title."},
|
||||
@@ -599,7 +609,7 @@ async def auto_name_session(session_manager, sess):
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=32,
|
||||
headers=_headers or None,
|
||||
headers=title_headers,
|
||||
timeout=20,
|
||||
thinking_mode="off",
|
||||
)
|
||||
@@ -625,6 +635,14 @@ async def auto_name_session(session_manager, sess):
|
||||
return
|
||||
|
||||
owner = getattr(sess, "owner", None)
|
||||
from src.owner_identity import auth_disabled
|
||||
if not owner and not auth_disabled():
|
||||
title = fallback_session_title(first_msg)
|
||||
session_manager.update_session_name(sess.id, title)
|
||||
logger.info(f"Auto-named unowned session {sess.id} deterministically: {title}")
|
||||
return
|
||||
|
||||
from src.task_endpoint import resolve_task_endpoint
|
||||
t_url, t_model, t_headers = resolve_task_endpoint(
|
||||
sess.endpoint_url, sess.model, sess.headers, owner=owner
|
||||
)
|
||||
@@ -632,24 +650,64 @@ async def auto_name_session(session_manager, sess):
|
||||
logger.debug("[auto-name] No model provided, skipping")
|
||||
return
|
||||
|
||||
sess_url = getattr(sess, "endpoint_url", None)
|
||||
if t_url and sess_url and t_url == sess_url:
|
||||
from src.database import SessionLocal
|
||||
from src.endpoint_resolver import (
|
||||
build_chat_url,
|
||||
build_headers,
|
||||
resolve_endpoint,
|
||||
resolve_endpoint_runtime,
|
||||
resolve_owner_registered_endpoint,
|
||||
)
|
||||
db = SessionLocal()
|
||||
try:
|
||||
endpoint = resolve_owner_registered_endpoint(db, t_url, owner)
|
||||
base, key = resolve_endpoint_runtime(endpoint, owner=owner)
|
||||
t_url = build_chat_url(base)
|
||||
t_headers = build_headers(key, base)
|
||||
except Exception as e:
|
||||
logger.debug("[auto-name] session fallback endpoint %s unauthorized for %s: %s", t_url, owner, e)
|
||||
t_url, t_model, t_headers = None, None, None
|
||||
for prefix in ("utility", "default"):
|
||||
try:
|
||||
u, m, h = resolve_endpoint(prefix, owner=owner)
|
||||
if u and m:
|
||||
t_url, t_model, t_headers = u, m, h
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if not t_url or not t_model:
|
||||
title = fallback_session_title(first_msg)
|
||||
session_manager.update_session_name(sess.id, title)
|
||||
logger.info(f"Auto-named session {sess.id} deterministically (no authorized model endpoint): {title}")
|
||||
return
|
||||
|
||||
# max_tokens big enough that reasoning models (Minimax M2,
|
||||
# DeepSeek R1, QwQ, etc.) have headroom for <think>…</think>
|
||||
# plus the actual title — 200 used to clip them mid-reasoning
|
||||
# so strip_think left an empty string and no rename happened.
|
||||
# Timeout matches: 60s gives slow local reasoners room to finish.
|
||||
title = await llm_call_async(
|
||||
t_url,
|
||||
t_model,
|
||||
[
|
||||
{"role": "system", "content": "Generate a short title (3-6 words, no quotes) for a conversation that starts with this message. Reply with ONLY the title, nothing else. Do NOT include any thinking, reasoning, or explanation — just the title."},
|
||||
{"role": "user", "content": first_msg},
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=64,
|
||||
headers=t_headers,
|
||||
timeout=15,
|
||||
reasoning_effort=effort_for_call(t_url, t_model, owner, sess),
|
||||
)
|
||||
try:
|
||||
title = await llm_call_async(
|
||||
t_url,
|
||||
t_model,
|
||||
[
|
||||
{"role": "system", "content": "Generate a short title (3-6 words, no quotes) for a conversation that starts with this message. Reply with ONLY the title, nothing else. Do NOT include any thinking, reasoning, or explanation — just the title."},
|
||||
{"role": "user", "content": first_msg},
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=64,
|
||||
headers=t_headers,
|
||||
timeout=15,
|
||||
reasoning_effort=effort_for_call(t_url, t_model, owner, sess),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("[auto-name] non-spec title call failed for %s: %s", sess.id, e)
|
||||
title = ""
|
||||
|
||||
title = title.strip().strip('"\'').strip()
|
||||
# Strip <think>/<thinking> blocks (closed, dangling, or stray tags)
|
||||
|
||||
Reference in New Issue
Block a user