From a2d417e0da286610582df01f562bff82466ee614 Mon Sep 17 00:00:00 2001 From: GGbond <2256433591@qq.com> Date: Mon, 14 Sep 2026 10:54:34 +0800 Subject: [PATCH] test(sandbox): render env-injection probes in the resolved shell's syntax on Windows (#5415) --- .../test_skill_request_scoped_secrets.py | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/tests/test_skill_request_scoped_secrets.py b/backend/tests/test_skill_request_scoped_secrets.py index 1675b9d81..9f9a4fce7 100644 --- a/backend/tests/test_skill_request_scoped_secrets.py +++ b/backend/tests/test_skill_request_scoped_secrets.py @@ -23,13 +23,30 @@ from deerflow.skills.types import SecretRequirement, Skill, SkillCategory _SLASH_SOURCE_OWNER_TOKEN = "test-slash-source-owner" +def _echo_env_probe(name: str) -> str: + """Render an env-var echo probe in the syntax of the shell LocalSandbox picks. + + The POSIX `$NAME` form expands under neither PowerShell nor cmd.exe, so on + Windows hosts the probe must use the resolved shell's own expansion syntax; + otherwise the variable echoes empty and the negative checks below silently + stop measuring anything (an unset PowerShell variable prints as a blank + line, which would let even a leaked secret pass). + """ + shell = LocalSandbox._get_shell() + if LocalSandbox._is_powershell(shell): + return f"echo [$env:{name}]" + if LocalSandbox._is_cmd_shell(shell): + return f"echo [%{name}%]" + return f"echo [${name}]" + + class TestLocalSandboxEnvInjection: """LocalSandbox.execute_command(env=...) injects per-call env into the subprocess.""" def test_injected_env_visible_to_command(self): sandbox = LocalSandbox(id="local") out = sandbox.execute_command( - "echo $DEERFLOW_TEST_SECRET", + _echo_env_probe("DEERFLOW_TEST_SECRET"), env={"DEERFLOW_TEST_SECRET": "s3cret-value"}, ) assert "s3cret-value" in out @@ -38,14 +55,14 @@ class TestLocalSandboxEnvInjection: """env=None preserves the legacy inherited-os.environ behaviour.""" monkeypatch.setenv("DEERFLOW_INHERITED_VAR", "inherited-value") sandbox = LocalSandbox(id="local") - out = sandbox.execute_command("echo $DEERFLOW_INHERITED_VAR") + out = sandbox.execute_command(_echo_env_probe("DEERFLOW_INHERITED_VAR")) assert "inherited-value" in out def test_injected_env_is_per_call_only(self): """Injected env must not leak into a subsequent call that does not pass it.""" sandbox = LocalSandbox(id="local") - sandbox.execute_command("true", env={"DEERFLOW_EPHEMERAL": "leaky"}) - out = sandbox.execute_command("echo [$DEERFLOW_EPHEMERAL]") + sandbox.execute_command(_echo_env_probe("DEERFLOW_EPHEMERAL"), env={"DEERFLOW_EPHEMERAL": "leaky"}) + out = sandbox.execute_command(_echo_env_probe("DEERFLOW_EPHEMERAL")) assert "leaky" not in out def test_platform_secret_scrubbed_from_inherited_env(self, monkeypatch): @@ -54,14 +71,14 @@ class TestLocalSandboxEnvInjection: is security theatre — a skill script could simply read $OPENAI_API_KEY.""" monkeypatch.setenv("OPENAI_API_KEY", "sk-platform-should-not-leak") sandbox = LocalSandbox(id="local") - out = sandbox.execute_command("echo [$OPENAI_API_KEY]") + out = sandbox.execute_command(_echo_env_probe("OPENAI_API_KEY")) assert "sk-platform-should-not-leak" not in out def test_benign_env_still_inherited_after_scrub(self, monkeypatch): """Scrubbing platform secrets must not strip harmless vars that skills rely on.""" monkeypatch.setenv("DEERFLOW_PLAIN_VAR", "harmless-value") sandbox = LocalSandbox(id="local") - out = sandbox.execute_command("echo [$DEERFLOW_PLAIN_VAR]") + out = sandbox.execute_command(_echo_env_probe("DEERFLOW_PLAIN_VAR")) assert "harmless-value" in out def test_injected_secret_survives_scrub(self, monkeypatch): @@ -69,7 +86,7 @@ class TestLocalSandboxEnvInjection: pattern — injection happens after scrubbing the inherited environment.""" sandbox = LocalSandbox(id="local") out = sandbox.execute_command( - "echo [$INJECTED_API_KEY]", + _echo_env_probe("INJECTED_API_KEY"), env={"INJECTED_API_KEY": "scoped-value"}, ) assert "scoped-value" in out @@ -247,6 +264,7 @@ class TestEnvPolicy: from deerflow.sandbox.env_policy import build_sandbox_env monkeypatch.setenv("MYSQL_URL", "mysql://user:pw@host/db") + monkeypatch.setenv("PWD", "/repo") # POSIX hosts set PWD themselves; plant it so the survival check runs on Windows too monkeypatch.setenv("MYSQL_PWD", "prod-db-password") monkeypatch.setenv("REDISCLI_AUTH", "prod-redis-auth") env = build_sandbox_env()