mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-04-28 06:19:36 +00:00
fix(aio-sandbox): redact env values in container logs (#2562)
* fix(aio-sandbox): redact env values in container logs Fixes #2534 * fix(aio-sandbox): address env log review comments
This commit is contained in:
parent
af8c0cfb78
commit
f7dfb88a30
2 changed files with 134 additions and 2 deletions
|
|
@ -9,6 +9,7 @@ from __future__ import annotations
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
|
||||
|
|
@ -86,6 +87,46 @@ def _format_container_mount(runtime: str, host_path: str, container_path: str, r
|
|||
return ["-v", mount_spec]
|
||||
|
||||
|
||||
def _redact_container_command_for_log(cmd: list[str]) -> list[str]:
|
||||
"""Return a Docker/Container command with environment values redacted."""
|
||||
redacted: list[str] = []
|
||||
redact_next_env = False
|
||||
|
||||
for arg in cmd:
|
||||
if redact_next_env:
|
||||
if "=" in arg:
|
||||
key = arg.split("=", 1)[0]
|
||||
redacted.append(f"{key}=<redacted>" if key else "<redacted>")
|
||||
else:
|
||||
redacted.append(arg)
|
||||
redact_next_env = False
|
||||
continue
|
||||
|
||||
if arg in {"-e", "--env"}:
|
||||
redacted.append(arg)
|
||||
redact_next_env = True
|
||||
continue
|
||||
|
||||
if arg.startswith("--env="):
|
||||
value = arg.removeprefix("--env=")
|
||||
if "=" in value:
|
||||
key = value.split("=", 1)[0]
|
||||
redacted.append(f"--env={key}=<redacted>" if key else "--env=<redacted>")
|
||||
else:
|
||||
redacted.append(arg)
|
||||
continue
|
||||
|
||||
redacted.append(arg)
|
||||
|
||||
return redacted
|
||||
|
||||
|
||||
def _format_container_command_for_log(cmd: list[str]) -> str:
|
||||
if os.name == "nt":
|
||||
return subprocess.list2cmdline(cmd)
|
||||
return shlex.join(cmd)
|
||||
|
||||
|
||||
class LocalContainerBackend(SandboxBackend):
|
||||
"""Backend that manages sandbox containers locally using Docker or Apple Container.
|
||||
|
||||
|
|
@ -464,7 +505,8 @@ class LocalContainerBackend(SandboxBackend):
|
|||
|
||||
cmd.append(self._image)
|
||||
|
||||
logger.info(f"Starting container using {self._runtime}: {' '.join(cmd)}")
|
||||
log_cmd = _format_container_command_for_log(_redact_container_command_for_log(cmd))
|
||||
logger.info(f"Starting container using {self._runtime}: {log_cmd}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
from deerflow.community.aio_sandbox.local_backend import _format_container_mount
|
||||
import logging
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
from deerflow.community.aio_sandbox.local_backend import LocalContainerBackend, _format_container_command_for_log, _format_container_mount, _redact_container_command_for_log
|
||||
|
||||
|
||||
def test_format_container_mount_uses_mount_syntax_for_docker_windows_paths():
|
||||
|
|
@ -26,3 +30,89 @@ def test_format_container_mount_keeps_volume_syntax_for_apple_container():
|
|||
"-v",
|
||||
"/host/path:/mnt/path:ro",
|
||||
]
|
||||
|
||||
|
||||
def test_redact_container_command_for_log_redacts_env_values():
|
||||
redacted = _redact_container_command_for_log(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"-e",
|
||||
"API_KEY=secret-value",
|
||||
"--env=TOKEN=token-value",
|
||||
"--name",
|
||||
"sandbox",
|
||||
"image",
|
||||
]
|
||||
)
|
||||
|
||||
assert "API_KEY=<redacted>" in redacted
|
||||
assert "--env=TOKEN=<redacted>" in redacted
|
||||
assert "secret-value" not in " ".join(redacted)
|
||||
assert "token-value" not in " ".join(redacted)
|
||||
|
||||
|
||||
def test_redact_container_command_for_log_keeps_inherited_env_names():
|
||||
redacted = _redact_container_command_for_log(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"-e",
|
||||
"API_KEY",
|
||||
"--env=TOKEN",
|
||||
"--name",
|
||||
"sandbox",
|
||||
"image",
|
||||
]
|
||||
)
|
||||
|
||||
assert redacted == [
|
||||
"docker",
|
||||
"run",
|
||||
"-e",
|
||||
"API_KEY",
|
||||
"--env=TOKEN",
|
||||
"--name",
|
||||
"sandbox",
|
||||
"image",
|
||||
]
|
||||
|
||||
|
||||
def test_format_container_command_for_log_uses_windows_quoting(monkeypatch):
|
||||
monkeypatch.setattr(os, "name", "nt")
|
||||
|
||||
command = _format_container_command_for_log(["docker", "run", "--name", "sandbox one", "image"])
|
||||
|
||||
assert command == 'docker run --name "sandbox one" image'
|
||||
|
||||
|
||||
def test_start_container_logs_redacted_env_values(monkeypatch, caplog):
|
||||
backend = LocalContainerBackend(
|
||||
image="sandbox:latest",
|
||||
base_port=8080,
|
||||
container_prefix="sandbox",
|
||||
config_mounts=[],
|
||||
environment={"API_KEY": "secret-value", "NORMAL": "visible-value"},
|
||||
)
|
||||
monkeypatch.setattr(backend, "_runtime", "docker")
|
||||
|
||||
captured_cmd: list[str] = []
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
captured_cmd.extend(cmd)
|
||||
return SimpleNamespace(stdout="container-id\n", stderr="", returncode=0)
|
||||
|
||||
monkeypatch.setattr("subprocess.run", fake_run)
|
||||
|
||||
with caplog.at_level(logging.INFO, logger="deerflow.community.aio_sandbox.local_backend"):
|
||||
backend._start_container("sandbox-test", 18080)
|
||||
|
||||
joined_cmd = " ".join(captured_cmd)
|
||||
assert "API_KEY=secret-value" in joined_cmd
|
||||
assert "NORMAL=visible-value" in joined_cmd
|
||||
|
||||
log_output = "\n".join(record.getMessage() for record in caplog.records)
|
||||
assert "API_KEY=<redacted>" in log_output
|
||||
assert "NORMAL=<redacted>" in log_output
|
||||
assert "secret-value" not in log_output
|
||||
assert "visible-value" not in log_output
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue