Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/secuscan/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,9 @@ async def read_stream():
line = await stdout.readline()
if line:
decoded_line = line.decode("utf-8", errors="replace")
output_lines.append(decoded_line)
await self._broadcast(task_id, "output", decoded_line)
safe_line = redact(decoded_line)
output_lines.append(safe_line)
await self._broadcast(task_id, "output", safe_line)

try:
await asyncio.wait_for(read_stream(), timeout=timeout)
Expand Down
51 changes: 51 additions & 0 deletions testing/backend/unit/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,3 +1446,54 @@ async def fake_command(*args, **kwargs):
assert original_inputs_seen.get("domain") == "example.com"
assert original_inputs_seen.get("__pinned_ip") == "93.184.216.34"
await db.disconnect()


@pytest.mark.asyncio
async def test_execute_command_redacts_secrets_before_broadcast():
executor = TaskExecutor()
task_id = "test-sse-redaction-1624"
queue = executor.subscribe(task_id)

async def mock_readline():
for line in [
b"Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.secret123\n",
b"port 80 is open\n",
b"api_key=supersecretkey12345\n",
None,
]:
yield line

mock_stdout = AsyncMock()
mock_stdout.readline = mock_readline().__anext__
mock_stdout.at_eof = MagicMock(side_effect=[False, False, False, True])

with patch.object(executor, "_process_pids", {}):
process = AsyncMock()
process.stdout = mock_stdout
process.returncode = 0

async def fake_create_subprocess(*args, **kwargs):
return process

with patch("asyncio.create_subprocess_exec", side_effect=fake_create_subprocess):
output, exit_code = await executor._execute_command(["echo", "test"], task_id, timeout=30)

events = []
while not queue.empty():
events.append(queue.get_nowait())

output_events = [e for e in events if e["type"] == "output"]
assert len(output_events) == 3

assert "[REDACTED]" in output_events[0]["data"]
assert "eyJhbGciOiJIUzI1NiJ9.secret123" not in output_events[0]["data"]
assert "Authorization: Bearer" in output_events[0]["data"]

assert "port 80 is open" in output_events[1]["data"]

assert "[REDACTED]" in output_events[2]["data"]
assert "supersecretkey12345" not in output_events[2]["data"]

assert "[REDACTED]" in output
assert "eyJhbGciOiJIUzI1NiJ9.secret123" not in output
assert "supersecretkey12345" not in output
Loading