package codexhome import ( "errors" "os" "path/filepath" "strings" ) var ErrOutsideCodexHome = errors.New("路径超出 Codex home") var ErrForbiddenPath = errors.New("禁止访问敏感路径") func ResolveInside(home string, rel string) (string, error) { if filepath.IsAbs(rel) { return "", ErrOutsideCodexHome } cleanHome, err := filepath.Abs(home) if err != nil { return "", err } candidate, err := filepath.Abs(filepath.Join(cleanHome, rel)) if err != nil { return "", err } relative, err := filepath.Rel(cleanHome, candidate) if err != nil { return "", err } if relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) { return "", ErrOutsideCodexHome } if IsForbidden(candidate, cleanHome) { return "", ErrForbiddenPath } if err := rejectSymlinkEscape(cleanHome, candidate); err != nil { return "", err } return candidate, nil } func ResolveAgentTOML(home string, fileName string) (string, error) { if filepath.IsAbs(fileName) || filepath.Dir(fileName) != "." || fileName == "" { return "", ErrOutsideCodexHome } if !strings.HasSuffix(strings.ToLower(fileName), ".toml") { return "", ErrForbiddenPath } return ResolveInside(home, filepath.Join("agents", fileName)) } func IsForbidden(path string, home string) bool { cleanHome, err := filepath.Abs(home) if err != nil { return true } cleanPath, err := filepath.Abs(path) if err != nil { return true } rel, err := filepath.Rel(cleanHome, cleanPath) if err != nil { return true } rel = strings.ToLower(filepath.ToSlash(rel)) forbidden := map[string]bool{ "auth.json": true, } return forbidden[rel] } func rejectSymlinkEscape(home string, candidate string) error { evaluatedHome, err := evalExistingPath(home) if err != nil { return err } rel, err := filepath.Rel(home, candidate) if err != nil { return err } if rel == "." { return nil } currentLexical := home currentEvaluated := evaluatedHome for _, part := range strings.Split(rel, string(filepath.Separator)) { currentLexical = filepath.Join(currentLexical, part) currentEvaluated = filepath.Join(currentEvaluated, part) info, err := os.Lstat(currentLexical) if err != nil { if os.IsNotExist(err) { continue } return err } if info.Mode()&os.ModeSymlink != 0 { currentEvaluated, err = filepath.EvalSymlinks(currentLexical) if err != nil { return ErrOutsideCodexHome } } if !isInside(evaluatedHome, currentEvaluated) { return ErrOutsideCodexHome } } return nil } func evalExistingPath(path string) (string, error) { evaluated, err := filepath.EvalSymlinks(path) if err == nil { return evaluated, nil } if !os.IsNotExist(err) { return "", err } parent := filepath.Dir(path) if parent == path { return filepath.Abs(path) } evaluatedParent, err := evalExistingPath(parent) if err != nil { return "", err } return filepath.Join(evaluatedParent, filepath.Base(path)), nil } func isInside(home string, path string) bool { rel, err := filepath.Rel(home, path) if err != nil { return false } return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))) }