The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/registry/apis/provisioning/safepath/safe.go

116 lines
2.9 KiB

package safepath
import (
"errors"
"regexp"
"strings"
)
var (
ErrPathTooLong = errors.New("path too long")
ErrInvalidCharacters = errors.New("path contains invalid characters")
ErrDoubleSlash = errors.New("path contains double slashes")
ErrInvalidFormat = errors.New("invalid path format")
ErrPercentChar = errors.New("path contains percent character which could be used for URL encoding attacks")
ErrHiddenPath = errors.New("path contains hidden file or directory (starting with dot)")
ErrPathTraversalAttempt = errors.New("path contains traversal attempt (./ or ../)")
)
const (
MaxPathLength = 1024 // Maximum allowed path length in characters
)
// validPathPattern matches valid path characters:
// - Alphanumeric (a-z, A-Z, 0-9)
// - Forward slash for path separation
// - Dots for file extensions and current directory
// - Underscores and hyphens for file/folder names
var validPathPattern = regexp.MustCompile(`^[a-zA-Z0-9 /_.-]+$`)
func IsSafe(path string) error {
// Check path length
if len(path) > MaxPathLength {
return ErrPathTooLong
}
// Empty path is valid (represents current directory)
if path == "" {
return nil
}
// Check specifically for percent character first
if strings.Contains(path, "%") {
return ErrPercentChar
}
// Check for invalid characters
if !validPathPattern.MatchString(path) {
return ErrInvalidCharacters
}
// Check for double slashes
if strings.Contains(path, "//") {
return ErrDoubleSlash
}
parts := Split(path)
for _, part := range parts {
// Check for path traversal attempts first
if part == ".." || part == "." {
return ErrPathTraversalAttempt
}
// Check for hidden files/directories in any part of the path
if part == "" || strings.HasPrefix(part, ".") {
return ErrHiddenPath
}
}
// If it's not a directory, it should have a filename component
if !IsDir(path) && len(parts) > 0 {
filename := parts[len(parts)-1]
if filename == "" {
return ErrInvalidFormat
}
}
return nil
}
// SafeSegment returns a safe part of the path
// It ensures the path is free from traversal attempts, hidden files,
// and other potentially dangerous patterns.
func SafeSegment(path string) string {
if path == "" {
return ""
}
parts := Split(path)
if len(parts) == 0 {
return ""
}
// Build up the path segment by segment, checking safety
var safePath string
for _, part := range parts {
// Check if this segment is safe
testPath := Join(safePath, part)
if IsSafe(testPath) != nil || part == "" {
// If this segment is unsafe, return the path up to but not including this segment
// Add trailing slash for directories
if safePath != "" {
return safePath + "/"
}
return ""
}
safePath = testPath
}
// If we made it through all segments, the path is safe
// Preserve trailing slash if original path had one
if IsDir(path) && safePath != "" {
return safePath + "/"
}
return safePath
}