Documentation
¶
Overview ¶
Package validate provides security validation for file access in the gosqlx CLI.
Overview ¶
This package implements comprehensive security checks for file operations to prevent common vulnerabilities including path traversal, symlink attacks, and resource exhaustion through large files.
Security Features ¶
## Path Traversal Prevention
Prevents directory traversal attacks using patterns like:
- ../../../etc/passwd
- ..\..\..\windows\system32
- Encoded variants (%2e%2e%2f)
Implementation:
- Resolves absolute paths before validation
- Checks for upward directory traversal
- Validates against working directory boundaries
## Symlink Validation
Prevents symlink-based attacks by:
- Resolving symlinks to their targets
- Validating target file properties
- Detecting circular symlinks
- Enforcing size limits on symlink targets
Protection against:
- Symlinks to sensitive system files
- Symlinks to files outside working directory
- Time-of-check to time-of-use (TOCTOU) attacks
## File Size Limits
Prevents resource exhaustion through:
- Default 10MB file size limit
- Configurable limits via .gosqlx.yml
- Pre-read size validation
- Memory-efficient file handling
Protects against:
- Denial of service (DoS) attacks
- Memory exhaustion
- Processing timeouts
## File Type Validation
Validates file types by:
- Checking file extensions (.sql, .txt)
- Detecting binary files (null byte scanning)
- Validating file permissions
- Checking file readability
Functions ¶
## ValidateInputFile
Comprehensive file validation with all security checks:
func ValidateInputFile(path string) error
Performs:
- Path traversal check
- Symlink resolution and validation
- File size limit enforcement
- File type validation
- Permission checks
Parameters:
- path: File path to validate
Returns:
- nil if file is safe to read
- error with specific security violation
Usage:
if err := validate.ValidateInputFile("query.sql"); err != nil {
return fmt.Errorf("security validation failed: %w", err)
}
content, _ := os.ReadFile("query.sql")
## ValidateFilePath
Validates file path for directory traversal:
func ValidateFilePath(path string) error
Checks:
- Absolute path resolution
- Upward directory traversal (../)
- Working directory boundaries
Usage:
if err := validate.ValidateFilePath(filePath); err != nil {
return fmt.Errorf("invalid file path: %w", err)
}
## ResolveAndValidateSymlink
Resolves symlinks and validates targets:
func ResolveAndValidateSymlink(path string) (string, error)
Performs:
- Symlink resolution to final target
- Target existence check
- Target size validation
- Circular symlink detection
Returns:
- Resolved file path
- Error if symlink validation fails
Usage:
resolvedPath, err := validate.ResolveAndValidateSymlink(filePath)
if err != nil {
return fmt.Errorf("symlink validation failed: %w", err)
}
content, _ := os.ReadFile(resolvedPath)
## ValidateFileSize
Enforces file size limits:
func ValidateFileSize(path string, maxSize int64) error
Checks:
- File size against limit
- File existence and readability
Parameters:
- path: File path to check
- maxSize: Maximum allowed file size in bytes
Returns:
- nil if file is within size limit
- error if file exceeds limit or is inaccessible
Usage:
maxSize := 10 * 1024 * 1024 // 10MB
if err := validate.ValidateFileSize(filePath, maxSize); err != nil {
return fmt.Errorf("file too large: %w", err)
}
Constants ¶
## DefaultMaxFileSize
Default maximum file size (10MB):
const DefaultMaxFileSize = 10 * 1024 * 1024
Rationale:
- Sufficient for typical SQL files
- Prevents memory exhaustion
- Configurable via .gosqlx.yml
Can be overridden in configuration:
validate:
security:
max_file_size: 20971520 # 20MB
Security Best Practices ¶
## Always Validate Before Reading
Validate file access before any file operations:
// INCORRECT - no validation
content, _ := os.ReadFile(userProvidedPath)
// CORRECT - validate first
if err := validate.ValidateInputFile(userProvidedPath); err != nil {
return err
}
content, _ := os.ReadFile(userProvidedPath)
## Use Validated Paths
Always use the validated/resolved path for file operations:
resolvedPath, err := validate.ResolveAndValidateSymlink(userPath)
if err != nil {
return err
}
// Use resolvedPath for all subsequent operations
content, _ := os.ReadFile(resolvedPath)
## Handle Validation Errors
Provide clear error messages without exposing system details:
if err := validate.ValidateInputFile(path); err != nil {
// DON'T expose system paths in error messages
return fmt.Errorf("file access validation failed")
// DO provide helpful context
return fmt.Errorf("file access validation failed: %w", err)
}
## Configuration Limits
Respect configured file size limits:
cfg, _ := config.LoadDefault()
maxSize := cfg.Validation.Security.MaxFileSize
if err := validate.ValidateFileSize(path, maxSize); err != nil {
return err
}
Attack Scenarios and Mitigations ¶
## Path Traversal Attack
Attack attempt:
gosqlx validate ../../../etc/passwd
Mitigation:
ValidateFilePath() rejects upward traversal: Error: "path traversal detected: file is outside working directory"
## Symlink Attack
Attack attempt:
ln -s /etc/passwd malicious.sql gosqlx validate malicious.sql
Mitigation:
ResolveAndValidateSymlink() validates target: Error: "symlink target is outside working directory"
## Resource Exhaustion
Attack attempt:
# Create 1GB file dd if=/dev/zero of=huge.sql bs=1M count=1024 gosqlx validate huge.sql
Mitigation:
ValidateFileSize() enforces limit: Error: "file size (1073741824 bytes) exceeds maximum (10485760 bytes)"
## Time-of-Check to Time-of-Use (TOCTOU)
Attack attempt:
# Replace file between validation and read gosqlx validate good.sql & sleep 0.1; ln -sf /etc/passwd good.sql
Mitigation:
- Symlink resolution before validation
- Immediate file operations after validation
- Operating system-level protections
Testing ¶
The package includes comprehensive security tests:
- security_test.go: Core security validation tests
- security_demo_test.go: Demonstration of security features
Test coverage includes:
- Path traversal attempts (various encodings)
- Symlink attack scenarios
- File size limit enforcement
- Edge cases (empty files, non-existent files)
- Platform-specific behavior (Windows vs Unix)
Platform Considerations ¶
## Unix/Linux/macOS
Path handling:
- Forward slash (/) as separator
- Symlink support (ReadLink, EvalSymlinks)
- Case-sensitive file systems (typically)
## Windows
Path handling:
- Backslash (\) as separator
- UNC paths (\\server\share)
- Case-insensitive file systems
- Limited symlink support (requires admin)
The package uses filepath.ToSlash and filepath.FromSlash for cross-platform compatibility.
Error Types ¶
Validation errors include specific context:
"path traversal detected: file is outside working directory" "symlink target is outside working directory" "file size (X bytes) exceeds maximum (Y bytes)" "file not found: path/to/file.sql" "permission denied: cannot read file"
Errors can be checked using errors.Is() for specific handling.
Performance ¶
Validation performance:
- Path validation: <1μs (path resolution)
- Symlink resolution: <10μs (filesystem stat)
- Size check: <1μs (metadata only, no read)
Total overhead: <20μs per file, negligible for typical workloads.
Integration ¶
This package is integrated into:
- cmd/gosqlx/cmd/input_utils.go (DetectAndReadInput, ValidateFileAccess)
- cmd/gosqlx/cmd/validator.go (file validation before processing)
- cmd/gosqlx/cmd/formatter.go (file validation before formatting)
- cmd/gosqlx/cmd/parser_cmd.go (file validation before parsing)
All file operations in the CLI use this validation layer.
Examples ¶
## Basic File Validation
import "github.com/ajitpratap0/GoSQLX/cmd/gosqlx/internal/validate"
func processFile(path string) error {
// Validate file before reading
if err := validate.ValidateInputFile(path); err != nil {
return fmt.Errorf("file validation failed: %w", err)
}
// Safe to read file
content, err := os.ReadFile(path)
if err != nil {
return err
}
// Process content
return processSQL(content)
}
## Symlink Handling
func processSymlink(path string) error {
// Resolve symlink to actual file
resolvedPath, err := validate.ResolveAndValidateSymlink(path)
if err != nil {
return fmt.Errorf("symlink validation failed: %w", err)
}
// Use resolved path
content, _ := os.ReadFile(resolvedPath)
return processSQL(content)
}
## Custom Size Limit
func processLargeFile(path string) error {
// Allow larger files (20MB)
maxSize := int64(20 * 1024 * 1024)
if err := validate.ValidateFileSize(path, maxSize); err != nil {
return fmt.Errorf("file too large: %w", err)
}
content, _ := os.ReadFile(path)
return processSQL(content)
}
See Also ¶
- cmd/gosqlx/cmd/input_utils.go - Input handling utilities
- cmd/gosqlx/internal/config/config.go - Configuration management
- https://owasp.org/www-community/attacks/Path_Traversal - Path traversal attacks
- https://cwe.mitre.org/data/definitions/59.html - Link following vulnerabilities
Index ¶
Constants ¶
const ( // MaxFileSize limits file size to prevent DoS attacks. // // Default: 10MB (10 * 1024 * 1024 bytes) // // This limit prevents: // - Memory exhaustion from loading large files // - Denial of service attacks // - Processing timeouts // // Can be configured in .gosqlx.yml: // // validate: // security: // max_file_size: 20971520 # 20MB MaxFileSize = 10 * 1024 * 1024 )
Variables ¶
This section is empty.
Functions ¶
func IsSecurePath ¶
IsSecurePath performs a quick check if a path looks secure.
Performs lightweight path validation without filesystem access, useful for early filtering before expensive validation. This is a heuristic check and should not be relied upon as the sole security measure.
Checks performed:
- No directory traversal sequences (..)
- No null bytes
- Not targeting sensitive system directories
Parameters:
- path: File path to check
Returns:
- true if path appears safe (passes heuristics)
- false if path contains suspicious patterns
Note: This is a preliminary check only. Always use ValidateInputFile or SecurityValidator.Validate for comprehensive security validation.
Example:
if !IsSecurePath(userInput) {
return errors.New("suspicious path detected")
}
// Still need full validation
if err := ValidateInputFile(userInput); err != nil {
return err
}
IsSecurePath performs a quick check if a path looks secure
func ValidateFileAccess ¶
ValidateFileAccess is a convenience function that validates file access.
This function provides backward compatibility with existing code that uses ValidateFileAccess. It delegates to ValidateInputFile for actual validation.
Parameters:
- path: File path to validate
Returns:
- nil if file is safe to access
- error if validation fails
This is equivalent to calling ValidateInputFile directly.
ValidateFileAccess is a convenience function that validates file access This is compatible with the existing ValidateFileAccess function in cmd
func ValidateInputFile ¶
ValidateInputFile performs comprehensive security validation on a file path.
This is the primary security entry point for file validation. It creates a SecurityValidator with default settings and validates the given file path.
Security checks performed:
- Path traversal prevention (../ sequences)
- Symlink resolution and validation
- File existence and accessibility
- Regular file check (not directory, device, etc.)
- File size limit enforcement (10MB default)
- File extension validation (.sql, .txt)
- Read permission verification
Parameters:
- path: File path to validate (absolute or relative)
Returns:
- nil if file is safe to read
- error with specific security violation details
Example:
if err := validate.ValidateInputFile("query.sql"); err != nil {
return fmt.Errorf("security check failed: %w", err)
}
// Safe to read file
content, _ := os.ReadFile("query.sql")
Security guarantees:
- File cannot be outside working directory (if symlink)
- File size is within configured limits
- File is a regular file with valid extension
- File is readable by current process
ValidateInputFile performs comprehensive security validation on a file path
Types ¶
type SecurityValidator ¶
type SecurityValidator struct {
MaxFileSize int64
AllowedExtensions []string
AllowSymlinks bool
WorkingDirectory string // Optional: restrict to working directory
}
SecurityValidator provides comprehensive file security validation.
Implements defense-in-depth security checks for file access including:
- Path traversal prevention
- Symlink validation
- File size limits
- File type validation
- Permission checks
Fields:
- MaxFileSize: Maximum allowed file size in bytes
- AllowedExtensions: Array of permitted file extensions (.sql, .txt)
- AllowSymlinks: Whether to allow symlink following (default: false)
- WorkingDirectory: Optional directory restriction for path validation
Thread Safety:
SecurityValidator instances are not thread-safe. Create separate instances for concurrent use or use appropriate synchronization.
func NewSecurityValidator ¶
func NewSecurityValidator() *SecurityValidator
NewSecurityValidator creates a validator with default security settings.
Returns a SecurityValidator configured with production-ready defaults:
- MaxFileSize: 10MB
- AllowedExtensions: .sql, .txt, and files without extension
- AllowSymlinks: false (symlinks rejected for security)
- WorkingDirectory: empty (no directory restriction)
Returns:
- *SecurityValidator with default configuration
Example:
validator := NewSecurityValidator()
if err := validator.Validate("query.sql"); err != nil {
log.Fatalf("Validation failed: %v", err)
}
Customization:
validator := NewSecurityValidator() validator.MaxFileSize = 20 * 1024 * 1024 // Allow 20MB files validator.AllowSymlinks = true // Allow symlinks validator.WorkingDirectory = "/safe/path" // Restrict to directory
NewSecurityValidator creates a validator with default security settings
func (*SecurityValidator) Validate ¶
func (v *SecurityValidator) Validate(path string) error
Validate performs comprehensive security checks on a file path.
This is the core validation method that performs all security checks in the correct order to prevent TOCTOU attacks and other vulnerabilities.
Validation sequence:
- Path traversal check on original path
- Symlink resolution to real path
- Symlink policy enforcement (if AllowSymlinks is false)
- File existence and accessibility check
- Regular file verification (not directory/device/socket)
- File size limit enforcement
- File extension validation
- Read permission test
Parameters:
- path: File path to validate
Returns:
- nil if all security checks pass
- error with specific check that failed
The validation is defensive and fails closed - any error results in rejection to maintain security guarantees.
Example:
validator := &SecurityValidator{
MaxFileSize: 5 * 1024 * 1024, // 5MB
AllowedExtensions: []string{".sql"},
AllowSymlinks: false,
WorkingDirectory: "/project/sql",
}
if err := validator.Validate("query.sql"); err != nil {
log.Printf("Validation failed: %v", err)
return err
}
Validate performs comprehensive security checks on a file path