test: agregar tests completos para write_file, list_directory, append_file, delete_file

44 tests cubriendo todas las nuevas tools de archivos y la tool existente.

Tests por tool:
- write_file (11): crear archivo, ReadOnly, path fuera de allowed, contenido >1MB,
  crear dirs padre, sobreescribir, path traversal, symlink escape, deny-by-default
- list_directory (9): listado plano y recursivo, limite 500 entries, symlinks fuera
  de allowed, path traversal, deny-by-default, no-directorio, dir vacio
- append_file (11): append a existente, crear si no existe, ReadOnly, path fuera,
  limite 10MB total, path traversal, symlink escape, crear dirs padre
- delete_file (9): borrar archivo, rechazar directorios, ReadOnly, path fuera,
  path traversal, symlink escape, archivo inexistente, deny-by-default

Tambien corrige resolveReal() para resolver paths con multiples niveles de
directorios inexistentes (necesario para MkdirAll en write/append).
This commit is contained in:
2026-04-08 23:04:13 +00:00
parent 931e6928f5
commit 3adaeb0f8c
5 changed files with 775 additions and 9 deletions
+25 -9
View File
@@ -49,19 +49,35 @@ func validateWritePath(absPath string, allowedPaths []string, readOnly bool) err
}
// resolveReal resolves symlinks for a path.
// If the exact path doesn't exist, it resolves the deepest existing ancestor
// and appends the remaining segments, preventing partial traversal.
// If the exact path doesn't exist, it walks up the tree to find the deepest
// existing ancestor, resolves its symlinks, and appends the remaining segments.
// This prevents partial traversal attacks via symlinks in non-existent paths.
func resolveReal(path string) (string, error) {
real, err := filepath.EvalSymlinks(path)
if err == nil {
return filepath.Clean(real), nil
}
// Path doesn't exist — resolve parent and append base.
parent := filepath.Dir(path)
base := filepath.Base(path)
realParent, err := filepath.EvalSymlinks(parent)
if err != nil {
return "", err
// Walk up to find the deepest existing ancestor.
cleaned := filepath.Clean(path)
var tail []string
cur := cleaned
for {
parent := filepath.Dir(cur)
tail = append([]string{filepath.Base(cur)}, tail...)
realParent, err := filepath.EvalSymlinks(parent)
if err == nil {
// Found an existing ancestor — rebuild the path.
result := realParent
for _, seg := range tail {
result = filepath.Join(result, seg)
}
return filepath.Clean(result), nil
}
if parent == cur {
// Reached the root without finding an existing ancestor.
return "", fmt.Errorf("cannot resolve any ancestor of %q", path)
}
cur = parent
}
return filepath.Clean(filepath.Join(realParent, base)), nil
}