mirror of
https://github.com/ivuorinen/gibidify.git
synced 2026-01-26 11:34:03 +00:00
feat(security): improve security features, fixes
This commit is contained in:
@@ -93,11 +93,13 @@ func (e *StructuredError) WithLine(line int) *StructuredError {
|
||||
}
|
||||
|
||||
// NewStructuredError creates a new structured error.
|
||||
func NewStructuredError(errorType ErrorType, code, message string) *StructuredError {
|
||||
func NewStructuredError(errorType ErrorType, code, message, filePath string, context map[string]interface{}) *StructuredError {
|
||||
return &StructuredError{
|
||||
Type: errorType,
|
||||
Code: code,
|
||||
Message: message,
|
||||
Type: errorType,
|
||||
Code: code,
|
||||
Message: message,
|
||||
FilePath: filePath,
|
||||
Context: context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,33 +166,43 @@ const (
|
||||
CodeValidationFormat = "FORMAT"
|
||||
CodeValidationFileType = "FILE_TYPE"
|
||||
CodeValidationSize = "SIZE_LIMIT"
|
||||
CodeValidationRequired = "REQUIRED"
|
||||
CodeValidationPath = "PATH_TRAVERSAL"
|
||||
|
||||
// Resource Limit Error Codes
|
||||
CodeResourceLimitFiles = "FILE_COUNT_LIMIT"
|
||||
CodeResourceLimitTotalSize = "TOTAL_SIZE_LIMIT"
|
||||
CodeResourceLimitTimeout = "TIMEOUT"
|
||||
CodeResourceLimitMemory = "MEMORY_LIMIT"
|
||||
CodeResourceLimitConcurrency = "CONCURRENCY_LIMIT"
|
||||
CodeResourceLimitRate = "RATE_LIMIT"
|
||||
)
|
||||
|
||||
// Predefined error constructors for common error scenarios
|
||||
|
||||
// NewCLIMissingSourceError creates a CLI error for missing source argument.
|
||||
func NewCLIMissingSourceError() *StructuredError {
|
||||
return NewStructuredError(ErrorTypeCLI, CodeCLIMissingSource, "usage: gibidify -source <source_directory> [--destination <output_file>] [--format=json|yaml|markdown]")
|
||||
return NewStructuredError(ErrorTypeCLI, CodeCLIMissingSource, "usage: gibidify -source <source_directory> [--destination <output_file>] [--format=json|yaml|markdown]", "", nil)
|
||||
}
|
||||
|
||||
// NewFileSystemError creates a file system error.
|
||||
func NewFileSystemError(code, message string) *StructuredError {
|
||||
return NewStructuredError(ErrorTypeFileSystem, code, message)
|
||||
return NewStructuredError(ErrorTypeFileSystem, code, message, "", nil)
|
||||
}
|
||||
|
||||
// NewProcessingError creates a processing error.
|
||||
func NewProcessingError(code, message string) *StructuredError {
|
||||
return NewStructuredError(ErrorTypeProcessing, code, message)
|
||||
return NewStructuredError(ErrorTypeProcessing, code, message, "", nil)
|
||||
}
|
||||
|
||||
// NewIOError creates an IO error.
|
||||
func NewIOError(code, message string) *StructuredError {
|
||||
return NewStructuredError(ErrorTypeIO, code, message)
|
||||
return NewStructuredError(ErrorTypeIO, code, message, "", nil)
|
||||
}
|
||||
|
||||
// NewValidationError creates a validation error.
|
||||
func NewValidationError(code, message string) *StructuredError {
|
||||
return NewStructuredError(ErrorTypeValidation, code, message)
|
||||
return NewStructuredError(ErrorTypeValidation, code, message, "", nil)
|
||||
}
|
||||
|
||||
// LogError logs an error with a consistent format if the error is not nil.
|
||||
|
||||
141
utils/paths.go
141
utils/paths.go
@@ -3,7 +3,9 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetAbsolutePath returns the absolute path for the given path.
|
||||
@@ -24,3 +26,142 @@ func GetBaseName(absPath string) string {
|
||||
}
|
||||
return baseName
|
||||
}
|
||||
|
||||
// ValidateSourcePath validates a source directory path for security.
|
||||
// It ensures the path exists, is a directory, and doesn't contain path traversal attempts.
|
||||
func ValidateSourcePath(path string) error {
|
||||
if path == "" {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationRequired, "source path is required", "", nil)
|
||||
}
|
||||
|
||||
// Check for path traversal patterns before cleaning
|
||||
if strings.Contains(path, "..") {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "path traversal attempt detected in source path", path, map[string]interface{}{
|
||||
"original_path": path,
|
||||
})
|
||||
}
|
||||
|
||||
// Clean and get absolute path
|
||||
cleaned := filepath.Clean(path)
|
||||
abs, err := filepath.Abs(cleaned)
|
||||
if err != nil {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSPathResolution, "cannot resolve source path", path, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Get current working directory to ensure we're not escaping it for relative paths
|
||||
if !filepath.IsAbs(path) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSPathResolution, "cannot get current working directory", path, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure the resolved path is within or below the current working directory
|
||||
cwdAbs, err := filepath.Abs(cwd)
|
||||
if err != nil {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSPathResolution, "cannot resolve current working directory", path, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Check if the absolute path tries to escape the current working directory
|
||||
if !strings.HasPrefix(abs, cwdAbs) {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "source path attempts to access directories outside current working directory", path, map[string]interface{}{
|
||||
"resolved_path": abs,
|
||||
"working_dir": cwdAbs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path exists and is a directory
|
||||
info, err := os.Stat(cleaned)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSNotFound, "source directory does not exist", path, nil)
|
||||
}
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSAccess, "cannot access source directory", path, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "source path must be a directory", path, map[string]interface{}{
|
||||
"is_file": true,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateDestinationPath validates a destination file path for security.
|
||||
// It ensures the path doesn't contain path traversal attempts and the parent directory exists.
|
||||
func ValidateDestinationPath(path string) error {
|
||||
if path == "" {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationRequired, "destination path is required", "", nil)
|
||||
}
|
||||
|
||||
// Check for path traversal patterns before cleaning
|
||||
if strings.Contains(path, "..") {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "path traversal attempt detected in destination path", path, map[string]interface{}{
|
||||
"original_path": path,
|
||||
})
|
||||
}
|
||||
|
||||
// Clean and validate the path
|
||||
cleaned := filepath.Clean(path)
|
||||
|
||||
// Get absolute path to ensure it's not trying to escape current working directory
|
||||
abs, err := filepath.Abs(cleaned)
|
||||
if err != nil {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSPathResolution, "cannot resolve destination path", path, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure the destination is not a directory
|
||||
if info, err := os.Stat(abs); err == nil && info.IsDir() {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "destination cannot be a directory", path, map[string]interface{}{
|
||||
"is_directory": true,
|
||||
})
|
||||
}
|
||||
|
||||
// Check if parent directory exists and is writable
|
||||
parentDir := filepath.Dir(abs)
|
||||
if parentInfo, err := os.Stat(parentDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSNotFound, "destination parent directory does not exist", path, map[string]interface{}{
|
||||
"parent_dir": parentDir,
|
||||
})
|
||||
}
|
||||
return NewStructuredError(ErrorTypeFileSystem, CodeFSAccess, "cannot access destination parent directory", path, map[string]interface{}{
|
||||
"parent_dir": parentDir,
|
||||
"error": err.Error(),
|
||||
})
|
||||
} else if !parentInfo.IsDir() {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "destination parent is not a directory", path, map[string]interface{}{
|
||||
"parent_dir": parentDir,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateConfigPath validates a configuration file path for security.
|
||||
// It ensures the path doesn't contain path traversal attempts.
|
||||
func ValidateConfigPath(path string) error {
|
||||
if path == "" {
|
||||
return nil // Empty path is allowed for config
|
||||
}
|
||||
|
||||
// Check for path traversal patterns before cleaning
|
||||
if strings.Contains(path, "..") {
|
||||
return NewStructuredError(ErrorTypeValidation, CodeValidationPath, "path traversal attempt detected in config path", path, map[string]interface{}{
|
||||
"original_path": path,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user