fix(exec): 沙箱子进程不再继承宿主完整环境(P0-7,审计 §4.3)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- buildEnv 不再以 os.Environ() 全集为基,改为「平台最小必需键白名单」裁剪后再叠加 req.Env
  · Windows:PATH/PATHEXT/SYSTEMROOT/WINDIR/COMSPEC/TEMP/TMP/USERPROFILE/HOMEDRIVE/HOMEPATH/APPDATA/LOCALAPPDATA/NUMBER_OF_PROCESSORS/OS/PROCESSOR_ARCHITECTURE
  · Unix:PATH/HOME/TMPDIR/LANG/LC_ALL/USER/SHELL
- 键名匹配在 Windows 上大小写不敏感(避免宿主写 Path 时漏掉),overlay 覆盖 base 时同口径去重
- envAllowed 语义不变:调用方能显式放行的键照旧,仅改变 base 来源
- 新增 TestTerminal_Env_HostNotInherited(非并行,t.Setenv):父进程设非白名单变量,断言子进程读不到;反证=把该键加进白名单立刻 FAIL
This commit is contained in:
AskaEth committed 2026-10-04 12:29:43 +08:00
1 parent 09ee561aa7
commit ed5bd92e1b
2 files changed
+130 -23

No files matched your search

+46
View File
@@ -296,6 +296,52 @@ func TestTerminal_Env_Whitelist(t *testing.T) {
}
}
// TestTerminal_Env_HostNotInherited 验证子进程不再继承宿主环境变量全集:宿主里
// 任意非白名单变量(如真实的 API key)都不会泄漏给沙箱命令;同时运行终端所必需的
// 基础键(PATH)仍在,否则命令无法启动。
// 注意:t.Setenv 与 t.Parallel 互斥,本用例不得并行。
func TestTerminal_Env_HostNotInherited(t *testing.T) {
requireBin(t, "env")
const (
secretName = "CYRENE_TEST_SECRET_XYZ"
secretValue = "s3cr3t-do-not-leak"
)
t.Setenv(secretName, secretValue)
ex := newEnabledExecutor(t, enabledCfg("env", t.TempDir()))
res, err := ex.Execute(context.Background(), ExecRequest{Command: []string{"env"}})
if err != nil {
t.Fatalf("Execute: %v", err)
}
if res.ExitCode != 0 {
t.Fatalf("ExitCode = %d, want 0; stderr=%q", res.ExitCode, res.Stderr)
}
if got := childEnvValue(res.Stdout, secretName); got != "" {
t.Errorf("子进程读到了非白名单宿主变量 %s=%q,宿主全集仍被透传", secretName, got)
}
if got := childEnvValue(res.Stdout, "PATH"); got == "" {
t.Errorf("子进程 PATH 为空,终端命令将无法解析可执行文件;stdout=%q", res.Stdout)
}
}
// childEnvValue 从 `env` 命令的输出里取 key 的值,未找到返回空串。键名比较走平台
// 口径(Windows 大小写不敏感),避免把宿主的 `Path` 拼写误判成缺失。
func childEnvValue(stdout, key string) string {
for _, line := range strings.Split(stdout, "\n") {
line = strings.TrimRight(line, "\r")
k, v, ok := strings.Cut(line, "=")
if !ok || k == "" {
continue
}
if envKeyEqual(k, key) {
return v
}
}
return ""
}
func TestTerminal_EmptyCommand(t *testing.T) {
t.Parallel()
ex := newEnabledExecutor(t, enabledCfg("echo", t.TempDir()))