security: remediate vulnerabilities and issues from codebase audit
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
This commit is contained in:
parent
b60a09196a
commit
fb3f3a3393
15 changed files with 465 additions and 208 deletions
|
|
@ -293,7 +293,7 @@ func (h *FSHandler) GetExtendedFileInfo(c *fiber.Ctx) error {
|
|||
// For files, attempt to get media metadata
|
||||
cat := categorizeFile(filepath.Ext(info.Name()))
|
||||
if cat == "images" || cat == "videos" || cat == "audio" {
|
||||
cmd := exec.Command("exiftool", "-json", resolvedPath)
|
||||
cmd := exec.Command("exiftool", "-json", "--", resolvedPath)
|
||||
output, err := cmd.Output()
|
||||
if err == nil {
|
||||
var meta []map[string]interface{}
|
||||
|
|
@ -327,7 +327,7 @@ func (h *FSHandler) CreateFolder(c *fiber.Ctx) error {
|
|||
parentDir := filepath.Dir(body.Path)
|
||||
folderName := filepath.Base(body.Path)
|
||||
|
||||
parentFullPath, err := h.resolveSafe(parentDir)
|
||||
parentFullPath, err := resolveSafe(h.Config.StorageDir, parentDir)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -371,7 +371,7 @@ func (h *FSHandler) Rename(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
oldFull, err := h.resolveSafe(body.OldPath)
|
||||
oldFull, err := resolveSafe(h.Config.StorageDir, body.OldPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -380,7 +380,7 @@ func (h *FSHandler) Rename(c *fiber.Ctx) error {
|
|||
uniqueName := GetUniquePath(parentDir, body.NewName)
|
||||
newPath := filepath.Join(filepath.Dir(body.OldPath), uniqueName)
|
||||
|
||||
newFull, err := h.resolveSafe(newPath)
|
||||
newFull, err := resolveSafe(h.Config.StorageDir, newPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -414,12 +414,12 @@ func (h *FSHandler) Move(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
srcFull, err := h.resolveSafe(body.SourcePath)
|
||||
srcFull, err := resolveSafe(h.Config.StorageDir, body.SourcePath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
dstFull, err := h.resolveSafe(body.DestPath)
|
||||
dstFull, err := resolveSafe(h.Config.StorageDir, body.DestPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -468,12 +468,12 @@ func (h *FSHandler) Copy(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
srcFull, err := h.resolveSafe(body.SourcePath)
|
||||
srcFull, err := resolveSafe(h.Config.StorageDir, body.SourcePath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
dstFull, err := h.resolveSafe(body.DestPath)
|
||||
dstFull, err := resolveSafe(h.Config.StorageDir, body.DestPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -643,7 +643,7 @@ func (h *FSHandler) DownloadFolder(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "path is required"})
|
||||
}
|
||||
|
||||
fullPath, err := h.resolveSafe(folderPath)
|
||||
fullPath, err := resolveSafe(h.Config.StorageDir, folderPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -722,7 +722,7 @@ func (h *FSHandler) DownloadBulk(c *fiber.Ctx) error {
|
|||
defer zipWriter.Close()
|
||||
|
||||
for _, p := range body.Paths {
|
||||
fullPath, err := h.resolveSafe(p)
|
||||
fullPath, err := resolveSafe(h.Config.StorageDir, p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -851,8 +851,26 @@ func (h *FSHandler) Search(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "query parameter 'q' is required"})
|
||||
}
|
||||
|
||||
// Append wildcard for prefix matching
|
||||
ftsQuery := query + "*"
|
||||
// Sanitize FTS5 query to prevent syntax errors
|
||||
words := strings.Fields(query)
|
||||
var safeWords []string
|
||||
for _, w := range words {
|
||||
// Keep only alphanumeric characters
|
||||
var clean strings.Builder
|
||||
for _, r := range w {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
||||
clean.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if clean.Len() > 0 {
|
||||
safeWords = append(safeWords, clean.String()+"*")
|
||||
}
|
||||
}
|
||||
|
||||
ftsQuery := strings.Join(safeWords, " AND ")
|
||||
if ftsQuery == "" {
|
||||
return c.JSON(fiber.Map{"items": []FileInfo{}, "count": 0})
|
||||
}
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT f.path, f.name, f.is_dir, f.size, f.mime_type, f.is_pinned
|
||||
|
|
@ -918,7 +936,7 @@ func (h *FSHandler) Upload(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "file is required"})
|
||||
}
|
||||
|
||||
fullDest, err := h.resolveSafe(destPath)
|
||||
fullDest, err := resolveSafe(h.Config.StorageDir, destPath)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
|
@ -1034,26 +1052,6 @@ func (h *FSHandler) StorageDashboard(c *fiber.Ctx) error {
|
|||
|
||||
// --- Helper functions ---
|
||||
|
||||
func (h *FSHandler) resolveSafe(relativePath string) (string, error) {
|
||||
cleaned := filepath.Clean(relativePath)
|
||||
if strings.Contains(cleaned, "..") {
|
||||
return "", fmt.Errorf("path traversal detected")
|
||||
}
|
||||
|
||||
full := filepath.Join(h.Config.StorageDir, cleaned)
|
||||
abs, err := filepath.Abs(full)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid path")
|
||||
}
|
||||
|
||||
absRoot, _ := filepath.Abs(h.Config.StorageDir)
|
||||
if abs != absRoot && !strings.HasPrefix(abs, absRoot+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("access denied")
|
||||
}
|
||||
|
||||
return abs, nil
|
||||
}
|
||||
|
||||
func generateChecksum(filePath string) (string, error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
|
|
@ -1132,6 +1130,18 @@ func (h *FSHandler) ServeThumbnail(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "missing checksum"})
|
||||
}
|
||||
|
||||
// Sanitize checksum: should only contain hex characters to prevent path traversal
|
||||
isHex := true
|
||||
for _, char := range checksum {
|
||||
if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) {
|
||||
isHex = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isHex {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid checksum format"})
|
||||
}
|
||||
|
||||
thumbPath := filepath.Join(h.Config.ThumbnailDir, checksum+".jpg")
|
||||
|
||||
if _, err := os.Stat(thumbPath); os.IsNotExist(err) {
|
||||
|
|
|
|||
Reference in a new issue