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:
agent
2026-06-07 11:50:13 +02:00
parent bb5b0e09b1
commit fc644ecd6e
308 changed files with 38829 additions and 474 deletions
+83
View File
@@ -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
}
}