What
Approving a pending terminal command lets that command run. Three paths approve commands, and none of them records a trustworthy approver:
api/agent_terminal.py, the approve-command route (around line 515): current_user = Depends(get_current_user) is injected but unused for attribution. service.approve_command(..., user_id=request.user_id, ...) takes the approver from the request body (TerminalApproveCommandRequest.user_id), so any authenticated caller can claim to be any user.
api/websockets.py _handle_command_approval (around lines 410-435): user_id = data.get("user_id", "web_user"). The approver comes from the WebSocket message payload, with a hard-coded fallback when it's absent.
api/security.py approve_command (around line 73): admin-gated at the router (check_admin_permission), but security_layer.approve_command(command_id, approved) records no approver at all.
All three reach services/command_execution_queue.approve_command(command_id, user_id, comment), which records whatever user_id it's given.
Not yet determined: whether paths 1 and 2 check that the approving user owns the session whose command is pending, or whether any authenticated user can approve another user's agent's command. Treat it as unverified until traced.
Found by the #17043 implementer (same class as #17042). Owner rules (2026-09-18, #17038): approvals are made by a human, and every decision leaves a trustworthy paper trail.
Acceptance criteria
What
Approving a pending terminal command lets that command run. Three paths approve commands, and none of them records a trustworthy approver:
api/agent_terminal.py, the approve-command route (around line 515):current_user = Depends(get_current_user)is injected but unused for attribution.service.approve_command(..., user_id=request.user_id, ...)takes the approver from the request body (TerminalApproveCommandRequest.user_id), so any authenticated caller can claim to be any user.api/websockets.py_handle_command_approval(around lines 410-435):user_id = data.get("user_id", "web_user"). The approver comes from the WebSocket message payload, with a hard-coded fallback when it's absent.api/security.pyapprove_command(around line 73): admin-gated at the router (check_admin_permission), butsecurity_layer.approve_command(command_id, approved)records no approver at all.All three reach
services/command_execution_queue.approve_command(command_id, user_id, comment), which records whateveruser_idit's given.Not yet determined: whether paths 1 and 2 check that the approving user owns the session whose command is pending, or whether any authenticated user can approve another user's agent's command. Treat it as unverified until traced.
Found by the #17043 implementer (same class as #17042). Owner rules (2026-09-18, #17038): approvals are made by a human, and every decision leaves a trustworthy paper trail.
Acceptance criteria
user_idare ignored with a logged warning.require_interactive_human), not a fork. A negative control per non-human credential type.