Fix workspace symlink escape validation
This commit is contained in:
parent
2acfc3d548
commit
def6a8f402
2 changed files with 49 additions and 0 deletions
|
|
@ -3,7 +3,9 @@ package workspace
|
|||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -204,6 +206,34 @@ func joinWithinRoot(root string, parts ...string) (string, error) {
|
|||
return "", os.ErrPermission
|
||||
}
|
||||
|
||||
func resolveWorkspacePath(rootPath, workspacePath string) error {
|
||||
resolvedRoot, err := filepath.EvalSymlinks(rootPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resolvedPath, err := filepath.EvalSymlinks(workspacePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// The workspace may not exist yet during creation. Resolve the root and
|
||||
// re-anchor the final entry under it so containment checks still compare
|
||||
// canonical paths.
|
||||
resolvedPath = filepath.Join(resolvedRoot, filepath.Base(workspacePath))
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(resolvedRoot, resolvedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return os.ErrPermission
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) workspacePath(op, name string) (string, error) {
|
||||
if name == "" {
|
||||
return "", coreerr.E(op, "workspace name is required", os.ErrInvalid)
|
||||
|
|
@ -215,6 +245,12 @@ func (s *Service) workspacePath(op, name string) (string, error) {
|
|||
if core.PathDir(path) != s.rootPath {
|
||||
return "", coreerr.E(op, "invalid workspace name: "+name, os.ErrPermission)
|
||||
}
|
||||
if err := resolveWorkspacePath(s.rootPath, path); err != nil {
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
return "", coreerr.E(op, "workspace path escapes root", err)
|
||||
}
|
||||
return "", coreerr.E(op, "failed to resolve workspace path", err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,19 @@ func TestSwitchWorkspace_TraversalBlocked(t *testing.T) {
|
|||
assert.Empty(t, s.activeWorkspace)
|
||||
}
|
||||
|
||||
func TestSwitchWorkspace_SymlinkEscapeBlocked(t *testing.T) {
|
||||
s, tempHome := newTestService(t)
|
||||
|
||||
outside := t.TempDir()
|
||||
linkPath := core.Path(tempHome, ".core", "workspaces", "escaped-link")
|
||||
require.NoError(t, os.Symlink(outside, linkPath))
|
||||
|
||||
err := s.SwitchWorkspace("escaped-link")
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, os.ErrPermission)
|
||||
assert.Empty(t, s.activeWorkspace)
|
||||
}
|
||||
|
||||
func TestWorkspaceFileSet_TraversalBlocked(t *testing.T) {
|
||||
s, tempHome := newTestService(t)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue