fix(security): sanitize env and redact output in quick commands + remove write-only _pending_messages

1. Quick command exec ran in the gateway process's full environment
   without env sanitization or output redaction. A quick command like
   "env" or "printenv" would leak all API keys, OAuth tokens, and
   bot credentials to the messaging user.

   Fix: apply _sanitize_subprocess_env() before exec and
   redact_sensitive_text() on output before returning.

2. GatewayRunner._pending_messages was written on every interrupt
   (lines 1331-1334) but never read or consumed anywhere. The actual
   interrupt delivery uses adapter._pending_messages (a separate dict).
   Removed the write-only accumulation to prevent unbounded growth.
This commit is contained in:
0xbyt4 2026-03-17 22:24:09 +03:00 committed by Teknium
parent 4c57a5b318
commit f6736ced81
2 changed files with 49 additions and 4 deletions

View file

@ -1,4 +1,5 @@
"""Tests for user-defined quick commands that bypass the agent loop."""
import os
import subprocess
from unittest.mock import MagicMock, patch, AsyncMock
from rich.text import Text
@ -159,6 +160,41 @@ class TestGatewayQuickCommands:
result = await runner._handle_message(event)
assert result == "ok"
@pytest.mark.asyncio
async def test_exec_command_does_not_leak_credentials(self):
"""Quick command exec must sanitize env — API keys must not appear in output."""
from gateway.run import GatewayRunner
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = {"quick_commands": {"leak": {"type": "exec", "command": "env"}}}
runner._running_agents = {}
runner._pending_messages = {}
runner._is_user_authorized = MagicMock(return_value=True)
event = self._make_event("leak")
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "sk-or-secret-12345"}):
result = await runner._handle_message(event)
assert "sk-or-secret-12345" not in result, \
"Quick command leaked OPENROUTER_API_KEY — exec runs without env sanitization"
@pytest.mark.asyncio
async def test_exec_command_output_is_redacted(self):
"""Quick command output must redact sensitive patterns before returning."""
from gateway.run import GatewayRunner
runner = GatewayRunner.__new__(GatewayRunner)
runner.config = {"quick_commands": {"token": {"type": "exec", "command": "echo sk-ant-api03-supersecretkey1234567890"}}}
runner._running_agents = {}
runner._pending_messages = {}
runner._is_user_authorized = MagicMock(return_value=True)
event = self._make_event("token")
result = await runner._handle_message(event)
assert "supersecretkey1234567890" not in result, \
"Quick command output not redacted — raw API key returned to user"
@pytest.mark.asyncio
async def test_unsupported_type_returns_error(self):
from gateway.run import GatewayRunner