fix(ingestion): confine /ingest-path and brain_ingest paths to brain/raw/
Both read a caller-chosen server path with extract.Text, so any file the pod could read (the ServiceAccount token, /etc/passwd) could be ingested into the brain and read back with brain_query. ResolveIngestPath resolves relative paths against brain/raw/ (the inbox the watcher already owns), evaluates symlinks, and refuses anything that lands outside. A directory ingest re-resolves every walked file, so a symlinked file inside raw/ cannot lead out. Existing ingest-path tests moved their fixtures under brain/raw/. Refs #149 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrOutsideIngestRoot means an ingest path does not resolve inside brain/raw/.
|
||||
var ErrOutsideIngestRoot = errors.New("ingest path must be inside brain/raw/")
|
||||
|
||||
// ResolveIngestPath confines a caller-supplied ingest path to brain/raw/, the
|
||||
// inbox the watcher already owns (hyperguild#149). Relative paths resolve
|
||||
// against that root; symlinks are evaluated before the containment check, so
|
||||
// a link inside raw/ that points elsewhere is refused. The path must exist.
|
||||
func ResolveIngestPath(brainDir, p string) (string, error) {
|
||||
if strings.TrimSpace(p) == "" {
|
||||
return "", fmt.Errorf("%w: path is empty", ErrOutsideIngestRoot)
|
||||
}
|
||||
root, err := filepath.EvalSymlinks(filepath.Join(brainDir, "raw"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: inbox unavailable: %v", ErrOutsideIngestRoot, err)
|
||||
}
|
||||
if !filepath.IsAbs(p) {
|
||||
p = filepath.Join(root, p)
|
||||
}
|
||||
real, err := filepath.EvalSymlinks(filepath.Clean(p))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %v", ErrOutsideIngestRoot, err)
|
||||
}
|
||||
rel, err := filepath.Rel(root, real)
|
||||
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
|
||||
return "", ErrOutsideIngestRoot
|
||||
}
|
||||
return real, nil
|
||||
}
|
||||
Reference in New Issue
Block a user