mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 06:52:20 +02:00
Merge commit from fork
* fix(security): stop API tokens reaching privileged agent tools A bearer API token resolves to the human who minted it, and minting is admin-only, so every owner-keyed privilege check in the agent path answers "admin". A token issued for a narrow integration therefore reached bash and python with the authority of the account that created it. Three independent routes to that sink, each closed here. The token could answer its own tool-approval prompt. An approval records that a person authorized one dangerous action, and a token cannot make that statement, so /api/chat_stream now refuses an approval resume from a bearer caller. The chat-session grant was reconstructable from caller-supplied message metadata. Two routes persist a metadata blob on the caller's behalf, so the shape of a resolved approval card could be written straight into a transcript and was then read back as authority. The server now signs the grant when it resolves an approval and verifies that signature when reading it back, binding it to the chat and the approval it was issued for. Both routes also drop server-owned keys from an inbound blob. A run driven by a token inherited its owner's tool set. Such a run is now capped at the non-admin policy regardless of who minted the credential, which holds even where no approval is raised at all. The human path is unchanged: a browser session still receives the prompt, still approves, and a granted chat-session scope still carries to later turns in that chat. Scope enforcement across the wider route surface is a separate gap and is not addressed here. * fix scoped chat delegation boundaries * fix(auth): reject malformed chat approval signatures --------- Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,7 @@ from src.settings import get_setting
|
||||
from src.prompt_security import untrusted_context_message
|
||||
from src.tool_security import (
|
||||
blocked_tools_for_owner,
|
||||
delegated_credential_blocked_tools,
|
||||
email_tool_policy_names,
|
||||
plan_mode_disabled_tools,
|
||||
)
|
||||
@@ -3443,6 +3444,7 @@ async def stream_agent_loop(
|
||||
uploaded_files: Optional[List[Dict]] = None,
|
||||
workload: str = "foreground",
|
||||
external_untrusted_context_seen: bool = False,
|
||||
delegated_credential: bool = False,
|
||||
exact_approval: Optional[ExactToolApproval] = None,
|
||||
_is_teacher_run: bool = False,
|
||||
history_session=None,
|
||||
@@ -3471,6 +3473,7 @@ async def stream_agent_loop(
|
||||
approval_gate_bypassed=bool(
|
||||
exact_approval and exact_approval.allow_remaining_actions
|
||||
),
|
||||
delegated_credential=bool(delegated_credential),
|
||||
)
|
||||
mcp_mgr = get_mcp_manager()
|
||||
prep_timings: Dict[str, float] = {}
|
||||
@@ -3490,6 +3493,10 @@ async def stream_agent_loop(
|
||||
mcp_mgr = None
|
||||
guide_only = bool(tool_policy and tool_policy.mode == "guide_only")
|
||||
public_blocked_tools = blocked_tools_for_owner(owner)
|
||||
if delegated_credential:
|
||||
# owner is the admin who minted the token, so the call above returns
|
||||
# nothing. Cap the run regardless of who it acts for.
|
||||
public_blocked_tools.update(delegated_credential_blocked_tools())
|
||||
if public_blocked_tools:
|
||||
disabled_tools.update(public_blocked_tools)
|
||||
# MCP tools are namespaced dynamically, so hide all MCP schemas for
|
||||
@@ -6434,6 +6441,10 @@ async def stream_agent_loop(
|
||||
tool_policy=tool_policy,
|
||||
active_document=active_document,
|
||||
active_email=active_email,
|
||||
external_untrusted_context_seen=(
|
||||
run_security.external_untrusted_context_seen
|
||||
),
|
||||
delegated_credential=delegated_credential,
|
||||
):
|
||||
yield evt
|
||||
except Exception as _esc_err:
|
||||
|
||||
Reference in New Issue
Block a user