mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-24 17:06:00 +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:
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime
|
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]
|
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):
|
class LocalContainerBackend(SandboxBackend):
|
||||||
"""Backend that manages sandbox containers locally using Docker or Apple Container.
|
"""Backend that manages sandbox containers locally using Docker or Apple Container.
|
||||||
|
|
||||||
@@ -464,7 +505,8 @@ class LocalContainerBackend(SandboxBackend):
|
|||||||
|
|
||||||
cmd.append(self._image)
|
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:
|
try:
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
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():
|
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",
|
"-v",
|
||||||
"/host/path:/mnt/path:ro",
|
"/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
|
||||||
|
|||||||
Reference in New Issue
Block a user