feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validatePath checks that absPath is under one of the allowed paths.
|
||||
// Deny-by-default: if allowedPaths is empty, no paths are allowed.
|
||||
// Resolves symlinks to prevent traversal via ../ or symlink escapes.
|
||||
func validatePath(absPath string, allowedPaths []string) error {
|
||||
if len(allowedPaths) == 0 {
|
||||
return fmt.Errorf("file: no allowed paths configured, all operations denied")
|
||||
}
|
||||
|
||||
// Resolve symlinks on the requested path to get the real path.
|
||||
// If the file doesn't exist yet, resolve the parent directory.
|
||||
realPath, err := resolveReal(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("file: cannot resolve path %q: %w", absPath, err)
|
||||
}
|
||||
|
||||
for _, allowed := range allowedPaths {
|
||||
a, err := filepath.Abs(allowed)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Resolve symlinks on the allowed path too.
|
||||
realAllowed, err := resolveReal(a)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Ensure the real path is strictly under the allowed directory.
|
||||
// Add trailing separator to prevent /opt matching /opt1234.
|
||||
if strings.HasPrefix(realPath, realAllowed+string(filepath.Separator)) || realPath == realAllowed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("path %q not under any allowed path", absPath)
|
||||
}
|
||||
|
||||
// validateWritePath checks path validity AND that writing is allowed.
|
||||
func validateWritePath(absPath string, allowedPaths []string, readOnly bool) error {
|
||||
if readOnly {
|
||||
return fmt.Errorf("file: write operations denied (read_only mode)")
|
||||
}
|
||||
return validatePath(absPath, allowedPaths)
|
||||
}
|
||||
|
||||
// resolveReal resolves symlinks for a path.
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user