Adds 13 security tests across 4 files for hardening verification: **Path Traversal Tests (path_traversal_test.go):** - TestSecurePathValidator_ValidRelativePath - TestSecurePathValidator_PathTraversalBlocked - TestSecurePathValidator_SymlinkEscape - Tests symlink resolution and path boundary enforcement **File Type Validation Tests (filetype_test.go):** - TestValidateFileType_AllowedTypes - TestValidateFileType_DangerousTypesBlocked - TestValidateModelFile - Tests magic bytes validation and dangerous extension blocking **Secrets Management Tests (secrets_test.go):** - TestExpandSecrets_BasicExpansion - TestExpandSecrets_NestedAndMissingVars - TestValidateNoPlaintextSecrets_HeuristicDetection - Tests env variable expansion and plaintext secret detection with entropy **Audit Logging Tests (audit_test.go):** - TestAuditLogger_ChainIntegrity - TestAuditLogger_VerifyChain - TestAuditLogger_LogFileAccess - TestAuditLogger_Disabled - Tests tamper-evident chain hashing and file access logging
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package security
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/fileutil"
|
|
)
|
|
|
|
func TestSecurePathValidator_ValidatePath(t *testing.T) {
|
|
// Create a temporary directory for testing
|
|
tempDir := t.TempDir()
|
|
validator := fileutil.NewSecurePathValidator(tempDir)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid relative path",
|
|
input: "subdir/file.txt",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid absolute path within base",
|
|
input: filepath.Join(tempDir, "file.txt"),
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "path traversal attempt with dots",
|
|
input: "../etc/passwd",
|
|
wantErr: true,
|
|
errMsg: "path escapes base directory",
|
|
},
|
|
{
|
|
name: "path traversal attempt with encoded dots",
|
|
input: "...//...//etc/passwd",
|
|
wantErr: true,
|
|
errMsg: "path escapes base directory",
|
|
},
|
|
{
|
|
name: "absolute path outside base",
|
|
input: "/etc/passwd",
|
|
wantErr: true,
|
|
errMsg: "path escapes base directory",
|
|
},
|
|
{
|
|
name: "empty path returns base",
|
|
input: "",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "single dot current directory",
|
|
input: ".",
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
// Create subdir for tests that need it
|
|
_ = os.MkdirAll(filepath.Join(tempDir, "subdir"), 0755)
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := validator.ValidatePath(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ValidatePath() error = nil, wantErr %v", tt.wantErr)
|
|
return
|
|
}
|
|
if tt.errMsg != "" && err.Error()[:len(tt.errMsg)] != tt.errMsg {
|
|
t.Errorf("ValidatePath() error = %v, want %v", err, tt.errMsg)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("ValidatePath() unexpected error = %v", err)
|
|
return
|
|
}
|
|
if got == "" {
|
|
t.Errorf("ValidatePath() returned empty path")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSecurePathValidator_SymlinkEscape(t *testing.T) {
|
|
// Create temp directories
|
|
tempDir := t.TempDir()
|
|
outsideDir := t.TempDir()
|
|
validator := fileutil.NewSecurePathValidator(tempDir)
|
|
|
|
// Create a file outside the base directory
|
|
outsideFile := filepath.Join(outsideDir, "secret.txt")
|
|
if err := os.WriteFile(outsideFile, []byte("secret"), 0600); err != nil {
|
|
t.Fatalf("Failed to create outside file: %v", err)
|
|
}
|
|
|
|
// Create a symlink inside tempDir pointing outside
|
|
symlinkPath := filepath.Join(tempDir, "link")
|
|
if err := os.Symlink(outsideFile, symlinkPath); err != nil {
|
|
t.Fatalf("Failed to create symlink: %v", err)
|
|
}
|
|
|
|
// Attempt to access through symlink should fail
|
|
_, err := validator.ValidatePath("link")
|
|
if err == nil {
|
|
t.Errorf("Symlink escape should be blocked: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSecurePathValidator_BasePathNotSet(t *testing.T) {
|
|
validator := fileutil.NewSecurePathValidator("")
|
|
_, err := validator.ValidatePath("test.txt")
|
|
if err == nil || err.Error() != "base path not set" {
|
|
t.Errorf("Expected 'base path not set' error, got: %v", err)
|
|
}
|
|
}
|