fix(volume-browser): address security review findings

Critical fixes:
- IDOR: verify volume belongs to project before resolving path
- Upload: override global 1MB body limit for upload endpoint (100MB)

High-priority fixes:
- Symlink escape: use filepath.EvalSymlinks in safePath validation
- Remove host filesystem path from browse API response
- Sanitize Content-Disposition filenames, force application/octet-stream
- Strip directory components from upload filenames
This commit is contained in:
2026-04-01 23:17:35 +03:00
parent aacdd255a9
commit 0491849f0f
4 changed files with 40 additions and 13 deletions
+11 -1
View File
@@ -168,6 +168,7 @@ func SaveFile(rootPath, relativePath string, r io.Reader) error {
}
// safePath resolves a relative path within rootPath and validates it doesn't escape.
// Resolves symlinks to prevent symlink-based traversal attacks.
func safePath(rootPath, relativePath string) (string, error) {
if relativePath == "" {
return rootPath, nil
@@ -181,15 +182,24 @@ func safePath(rootPath, relativePath string) (string, error) {
absPath := filepath.Join(rootPath, cleaned)
// Double-check the resolved path is within the root.
// Resolve the root path (follow symlinks in the root itself).
absRoot, err := filepath.Abs(rootPath)
if err != nil {
return "", fmt.Errorf("resolve root: %w", err)
}
if realRoot, err := filepath.EvalSymlinks(absRoot); err == nil {
absRoot = realRoot
}
// Resolve the target path including symlinks.
absResolved, err := filepath.Abs(absPath)
if err != nil {
return "", fmt.Errorf("resolve path: %w", err)
}
if realResolved, err := filepath.EvalSymlinks(absResolved); err == nil {
absResolved = realResolved
}
if !strings.HasPrefix(absResolved, absRoot) {
return "", fmt.Errorf("path traversal not allowed")
}