- Implement anomaly detection monitor (brute force, path traversal, etc.) - Add input validation framework with safety rules - Add environment-based secrets manager with redaction - Add security test suite for path traversal and injection - Add CI security scanning workflow
183 lines
4.5 KiB
Go
183 lines
4.5 KiB
Go
// Package validation provides input validation utilities for security
|
|
package validation
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// ValidationRule is a function that validates a string value
|
|
type ValidationRule func(value string) error
|
|
|
|
// Validator provides reusable validation rules
|
|
type Validator struct {
|
|
errors []string
|
|
}
|
|
|
|
// NewValidator creates a new validator
|
|
func NewValidator() *Validator {
|
|
return &Validator{errors: make([]string, 0)}
|
|
}
|
|
|
|
// Add adds a field to validate with the given rules
|
|
func (v *Validator) Add(name, value string, rules ...ValidationRule) {
|
|
for _, rule := range rules {
|
|
if err := rule(value); err != nil {
|
|
v.errors = append(v.errors, fmt.Sprintf("%s: %v", name, err))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Valid returns nil if validation passed, otherwise returns an error
|
|
func (v *Validator) Valid() error {
|
|
if len(v.errors) > 0 {
|
|
return fmt.Errorf("validation failed: %s", strings.Join(v.errors, "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Common validation rules
|
|
|
|
// SafeName validates alphanumeric + underscore + hyphen only
|
|
var SafeName ValidationRule = func(v string) error {
|
|
if matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, v); !matched {
|
|
return fmt.Errorf("must contain only alphanumeric characters, underscores, and hyphens")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MaxLength validates maximum string length
|
|
func MaxLength(max int) ValidationRule {
|
|
return func(v string) error {
|
|
if len(v) > max {
|
|
return fmt.Errorf("exceeds maximum length of %d", max)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MinLength validates minimum string length
|
|
func MinLength(min int) ValidationRule {
|
|
return func(v string) error {
|
|
if len(v) < min {
|
|
return fmt.Errorf("must be at least %d characters", min)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// NoPathTraversal validates no path traversal sequences
|
|
var NoPathTraversal ValidationRule = func(v string) error {
|
|
if strings.Contains(v, "..") || strings.Contains(v, "../") || strings.Contains(v, "..\\") {
|
|
return fmt.Errorf("path traversal sequence detected")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NoShellMetacharacters validates no shell metacharacters
|
|
var NoShellMetacharacters ValidationRule = func(v string) error {
|
|
dangerous := []string{";", "|", "&", "`", "$", "(", ")", "<", ">", "*", "?"}
|
|
for _, char := range dangerous {
|
|
if strings.Contains(v, char) {
|
|
return fmt.Errorf("shell metacharacter '%s' detected", char)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NoNullBytes validates no null bytes
|
|
var NoNullBytes ValidationRule = func(v string) error {
|
|
if strings.Contains(v, "\x00") {
|
|
return fmt.Errorf("null byte detected")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidPath validates a path is within a base directory
|
|
func ValidPath(basePath string) ValidationRule {
|
|
return func(v string) error {
|
|
cleaned := filepath.Clean(v)
|
|
absPath, err := filepath.Abs(cleaned)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid path: %w", err)
|
|
}
|
|
absBase, err := filepath.Abs(basePath)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid base path: %w", err)
|
|
}
|
|
if !strings.HasPrefix(absPath, absBase) {
|
|
return fmt.Errorf("path escapes base directory")
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MatchesPattern validates against a regex pattern
|
|
func MatchesPattern(pattern, description string) ValidationRule {
|
|
re := regexp.MustCompile(pattern)
|
|
return func(v string) error {
|
|
if !re.MatchString(v) {
|
|
return fmt.Errorf("must match pattern: %s", description)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Whitelist validates against a whitelist of allowed values
|
|
func Whitelist(allowed ...string) ValidationRule {
|
|
return func(v string) error {
|
|
for _, a := range allowed {
|
|
if v == a {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("value not in whitelist")
|
|
}
|
|
}
|
|
|
|
// Sanitize removes dangerous characters from input
|
|
func Sanitize(input string) string {
|
|
// Remove null bytes
|
|
input = strings.ReplaceAll(input, "\x00", "")
|
|
// Remove control characters
|
|
input = strings.ReplaceAll(input, "\r", "")
|
|
return input
|
|
}
|
|
|
|
// ValidateJobName validates a job name is safe
|
|
func ValidateJobName(jobName string) error {
|
|
validator := NewValidator()
|
|
validator.Add("job_name", jobName,
|
|
MinLength(1),
|
|
MaxLength(64),
|
|
SafeName,
|
|
NoPathTraversal,
|
|
NoShellMetacharacters,
|
|
)
|
|
return validator.Valid()
|
|
}
|
|
|
|
// ValidateExperimentID validates an experiment ID is safe
|
|
func ValidateExperimentID(id string) error {
|
|
validator := NewValidator()
|
|
validator.Add("experiment_id", id,
|
|
MinLength(1),
|
|
MaxLength(128),
|
|
SafeName,
|
|
NoPathTraversal,
|
|
)
|
|
return validator.Valid()
|
|
}
|
|
|
|
// ValidateCommand validates a command string is safe
|
|
func ValidateCommand(cmd string) error {
|
|
validator := NewValidator()
|
|
validator.Add("command", cmd,
|
|
MinLength(1),
|
|
MaxLength(1024),
|
|
NoShellMetacharacters,
|
|
)
|
|
return validator.Valid()
|
|
}
|