Files
cv-site/internal/handlers/cv_security_test.go
T
2025-11-11 13:53:14 +00:00

147 lines
3.7 KiB
Go

package handlers
import (
"os"
"path/filepath"
"testing"
)
// TestValidateRepoPath tests the security validation for repository paths
func TestValidateRepoPath(t *testing.T) {
// Get project root (two levels up from handlers directory)
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
// Navigate to project root
projectRoot := filepath.Join(cwd, "..", "..")
tests := []struct {
name string
path string
shouldErr bool
errMsg string
}{
{
name: "Valid path within project",
path: projectRoot,
shouldErr: false,
},
{
name: "Valid subdirectory",
path: filepath.Join(projectRoot, "data"),
shouldErr: false,
},
{
name: "Path traversal attack - parent directory",
path: "../../../etc/passwd",
shouldErr: true,
errMsg: "repository path outside project directory",
},
{
name: "Path traversal attack - absolute path",
path: "/etc/passwd",
shouldErr: true,
errMsg: "repository path outside project directory",
},
{
name: "Command injection attempt - pipe",
path: "data | cat /etc/passwd",
shouldErr: true,
errMsg: "path does not exist",
},
{
name: "Command injection attempt - semicolon",
path: "data; rm -rf /",
shouldErr: true,
errMsg: "path does not exist",
},
{
name: "Command injection attempt - backticks",
path: "data`whoami`",
shouldErr: true,
errMsg: "path does not exist",
},
{
name: "Non-existent path",
path: filepath.Join(projectRoot, "nonexistent-directory-12345"),
shouldErr: true,
errMsg: "path does not exist",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateRepoPath(tt.path)
if tt.shouldErr {
if err == nil {
t.Errorf("Expected error for path %q, got nil", tt.path)
return
}
if tt.errMsg != "" && err.Error() != "" {
// Check if error message contains expected substring
if !contains(err.Error(), tt.errMsg) {
t.Errorf("Expected error containing %q, got %q", tt.errMsg, err.Error())
}
}
} else {
if err != nil {
t.Errorf("Unexpected error for valid path %q: %v", tt.path, err)
}
}
})
}
}
// TestGetGitRepoFirstCommitDate_SecurityValidation tests that malicious paths are rejected
func TestGetGitRepoFirstCommitDate_SecurityValidation(t *testing.T) {
maliciousPaths := []string{
"../../../etc/passwd",
"/etc/passwd",
"data | cat /etc/passwd",
"data; whoami",
"data`id`",
"$(whoami)",
}
for _, path := range maliciousPaths {
t.Run("Reject_"+path, func(t *testing.T) {
// Should return empty string (safe rejection)
result := getGitRepoFirstCommitDate(path)
if result != "" {
t.Errorf("Expected empty result for malicious path %q, got %q", path, result)
}
})
}
}
// TestGetGitRepoFirstCommitDate_Timeout tests that git commands timeout appropriately
func TestGetGitRepoFirstCommitDate_Timeout(t *testing.T) {
// Create a temporary directory that exists but is not a git repo
tempDir := t.TempDir()
// This should timeout/fail gracefully (not hang)
result := getGitRepoFirstCommitDate(tempDir)
// Should return empty string for non-git repos
if result != "" {
t.Errorf("Expected empty result for non-git directory, got %q", result)
}
}
// Helper function to check if a string contains a substring
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
(len(s) > 0 && len(substr) > 0 && stringContains(s, substr)))
}
func stringContains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}