Files
weichenandCursor b85a1e1ca3 Add immutable layered Python environments for the sandbox.
Why: The sandbox hides the host home directory and has no network, so pip install --user cannot supply python-docx, and the service PATH resolved python3 to a linuxbrew path that does not exist inside the sandbox.
Evidence: SELF go test of execenv, tools, store, app, server, runmgr, config, and cmd/agent-system on this worktree. CI has not run.
Impact: Sessions inherit a read-only base layer. Extra packages are requested through python_env, auto-approved only from the allowlist, and mounted per conversation. Schema 35 adds the request and binding tables.
Refs: .task/feature-layered-python-env.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 13:52:48 +00:00

98 lines
2.5 KiB
Go

package execenv
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
// Doctor 用给定解释器导入模块。sandbox 为真且系统有 bwrap 时,按与执行后端相同的隔离方式跑。
func Doctor(ctx context.Context, python string, pyPath []string, modules []string, sandbox bool, mounts []string) error {
if python == "" {
return fmt.Errorf("execenv: doctor: python is empty")
}
if len(modules) == 0 {
modules = []string{"docx", "openpyxl", "pptx"}
}
script := "import " + strings.Join(modules, ", ")
if !sandbox {
cmd := exec.CommandContext(ctx, python, "-c", script)
cmd.Env = doctorEnv(pyPath)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("execenv: doctor: %v: %s", err, trim(out))
}
return nil
}
bwrap, err := exec.LookPath("bwrap")
if err != nil {
return fmt.Errorf("execenv: doctor: sandbox requested but bwrap is missing: %w", err)
}
argv := []string{"--unshare-all", "--die-with-parent", "--ro-bind", "/", "/", "--tmpfs", "/home", "--tmpfs", "/tmp", "--proc", "/proc", "--dev", "/dev"}
for _, m := range mounts {
if m == "" {
continue
}
argv = append(argv, "--ro-bind", m, m)
}
argv = append(argv, "--clearenv")
for _, kv := range doctorEnv(pyPath) {
if i := strings.IndexByte(kv, '='); i > 0 {
argv = append(argv, "--setenv", kv[:i], kv[i+1:])
}
}
argv = append(argv, python, "-c", script)
cmd := exec.CommandContext(ctx, bwrap, argv...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("execenv: doctor sandbox: %v: %s", err, trim(out))
}
return nil
}
func doctorEnv(pyPath []string) []string {
env := []string{
"PATH=/usr/local/bin:/usr/bin:/bin",
"PYTHONDONTWRITEBYTECODE=1",
"PIP_NO_INDEX=1",
}
if len(pyPath) > 0 {
env = append(env, "PYTHONPATH="+strings.Join(pyPath, string(os.PathListSeparator)))
}
return env
}
func trim(b []byte) string {
s := string(bytes.TrimSpace(b))
if len(s) > 500 {
s = s[:500]
}
return s
}
// EnsurePython 优先下载独立 CPython;失败则用系统 python 建一份 venv。
func (r *Root) EnsurePython(ctx context.Context) (string, error) {
if bin, ok := r.PythonBin(); ok {
return bin, nil
}
pctx, cancel := context.WithTimeout(ctx, 4*time.Minute)
defer cancel()
if err := r.InstallStandalone(pctx, ""); err == nil {
if bin, ok := r.PythonBin(); ok {
return bin, nil
}
}
if err := r.BootstrapVenv("/usr/bin/python3"); err != nil {
return "", err
}
bin, ok := r.PythonBin()
if !ok {
return "", fmt.Errorf("execenv: managed python missing after bootstrap")
}
return bin, nil
}