package runtime import ( "fmt" "os" "path/filepath" "strings" "browser.local/run/protocol" ) const maxExecutionContentBytes = 64 * 1024 type WorkspaceResolver struct { root string } func NewWorkspaceResolver(root string) WorkspaceResolver { if strings.TrimSpace(root) == "" { root = filepath.Join(".", ".run-workspace") } return WorkspaceResolver{root: root} } func (resolver WorkspaceResolver) Scope(serverInstanceID string, profileKey string) (string, error) { if err := validateWorkspaceComponent(serverInstanceID, "serverInstanceId"); err != nil { return "", err } if err := validateWorkspaceComponent(profileKey, "profileKey"); err != nil { return "", err } root, err := filepath.Abs(resolver.root) if err != nil { return "", fmt.Errorf("resolve workspace root: %w", err) } if err := ensureDirectory(root); err != nil { return "", fmt.Errorf("secure workspace root: %w", err) } instances := filepath.Join(root, "instances") if err := ensureDirectory(instances); err != nil { return "", fmt.Errorf("secure workspace instances: %w", err) } serverDir := filepath.Join(instances, serverInstanceID) if err := ensureDirectory(serverDir); err != nil { return "", fmt.Errorf("secure server workspace: %w", err) } scope := filepath.Join(serverDir, profileKey) if err := ensureDirectory(scope); err != nil { return "", fmt.Errorf("secure profile workspace: %w", err) } return scope, nil } func (resolver WorkspaceResolver) ExistingTarget(scope string, key string) (string, error) { path, err := resolver.target(scope, key, false) if err != nil { return "", err } info, err := os.Lstat(path) if err != nil { return "", err } if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() { return "", fmt.Errorf("target must be a regular file") } return path, nil } func (resolver WorkspaceResolver) ExistingDirectory(scope string, key string) (string, error) { if strings.TrimSpace(scope) == "" || !protocol.ValidLogicalFileKey(key) || filepath.IsAbs(key) || strings.Contains(key, string(rune(92))) { return "", fmt.Errorf("logical directory is unsafe") } cleanScope, err := filepath.Abs(scope) if err != nil { return "", err } root, err := filepath.Abs(resolver.root) if err != nil { return "", err } rel, err := filepath.Rel(root, cleanScope) if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) { return "", fmt.Errorf("workspace scope escapes root") } current := cleanScope for _, part := range strings.Split(filepath.ToSlash(key), "/") { if part == "" || part == "." || part == ".." { return "", fmt.Errorf("logical directory contains unsafe component") } current = filepath.Join(current, part) info, statErr := os.Lstat(current) if statErr != nil { return "", statErr } if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return "", fmt.Errorf("logical directory is not a real directory") } } return current, nil } func (resolver WorkspaceResolver) WritableTarget(scope string, key string) (string, string, error) { if strings.HasPrefix(key, "actions/") || strings.HasPrefix(key, "state/") || key == "actions" || key == "state" { return "", "", fmt.Errorf("target is reserved") } parts := strings.Split(filepath.ToSlash(key), "/") parent := scope for _, part := range parts[:len(parts)-1] { parent = filepath.Join(parent, part) if err := ensureDirectory(parent); err != nil { return "", "", err } } path, err := resolver.target(scope, key, true) if err != nil { return "", "", err } return path, filepath.Dir(path), nil } func (resolver WorkspaceResolver) target(scope string, key string, allowMissingFinal bool) (string, error) { if strings.TrimSpace(scope) == "" { return "", fmt.Errorf("workspace scope is invalid") } if !protocol.ValidLogicalFileKey(key) || filepath.IsAbs(key) || strings.Contains(key, `\`) { return "", fmt.Errorf("logical key is unsafe") } cleanScope, err := filepath.Abs(scope) if err != nil { return "", err } root, err := filepath.Abs(resolver.root) if err != nil { return "", err } rel, err := filepath.Rel(root, cleanScope) if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) { return "", fmt.Errorf("workspace scope escapes root") } parts := strings.Split(filepath.ToSlash(key), "/") current := cleanScope for index, part := range parts { if part == "" || part == "." || part == ".." { return "", fmt.Errorf("logical key contains unsafe component") } current = filepath.Join(current, part) info, statErr := os.Lstat(current) if statErr != nil { if allowMissingFinal && index == len(parts)-1 && os.IsNotExist(statErr) { return current, nil } return "", statErr } if info.Mode()&os.ModeSymlink != 0 { return "", fmt.Errorf("logical key contains a symlink") } if index < len(parts)-1 && !info.IsDir() { return "", fmt.Errorf("logical key parent is not a directory") } if index == len(parts)-1 && info.Mode()&os.ModeType != 0 { return "", fmt.Errorf("target is a special file") } } return current, nil } func ensureDirectory(path string) error { if info, err := os.Lstat(path); err == nil { if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return fmt.Errorf("path is not a real directory") } return os.Chmod(path, 0o700) } else if !os.IsNotExist(err) { return err } parent := filepath.Dir(path) if parent != path { if err := ensureDirectory(parent); err != nil { return err } } if err := os.Mkdir(path, 0o700); err != nil && !os.IsExist(err) { return err } info, err := os.Lstat(path) if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return fmt.Errorf("created path is not a real directory") } return os.Chmod(path, 0o700) } func validateWorkspaceComponent(value string, field string) error { if strings.TrimSpace(value) == "" || value != strings.TrimSpace(value) || value == "." || value == ".." || strings.ContainsAny(value, `/\`) || !protocol.ValidLogicalFileKey(value) { return fmt.Errorf("%s is unsafe", field) } return nil }