Files
cheat/internal/cheatpath/validate_fuzz_test.go
Christopher Allen Lane cc85a4bdb1 chore: bump version to 4.5.0
Bug fixes:
- Fix inverted pager detection logic (returned error instead of path)
- Fix repo.Clone ignoring destination directory parameter
- Fix sheet loading using append on pre-sized slices
- Clean up partial files on copy failure
- Trim whitespace from editor config

Security:
- Add path traversal protection for cheatsheet names

Performance:
- Move regex compilation outside search loop
- Replace string concatenation with strings.Join in search

Build:
- Remove go:generate; embed config and usage as string literals
- Parallelize release builds
- Add fuzz testing infrastructure

Testing:
- Improve test coverage from 38.9% to 50.2%
- Add fuzz tests for search, filter, tags, and validation

Documentation:
- Fix inaccurate code examples in HACKING.md
- Add missing --conf and --all options to man page
- Add ADRs for path traversal, env parsing, and search parallelization
- Update CONTRIBUTING.md to reflect project policy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 19:56:19 -05:00

170 lines
4.0 KiB
Go

package cheatpath
import (
"strings"
"testing"
"unicode/utf8"
)
// FuzzValidateSheetName tests the ValidateSheetName function with fuzzing
// to ensure it properly prevents path traversal and other security issues
func FuzzValidateSheetName(f *testing.F) {
// Add seed corpus with various valid and malicious inputs
// Valid names
f.Add("docker")
f.Add("docker/compose")
f.Add("lang/go/slice")
f.Add("my-cheat_sheet")
f.Add("file.txt")
f.Add("a")
f.Add("123")
// Path traversal attempts
f.Add("..")
f.Add("../etc/passwd")
f.Add("foo/../bar")
f.Add("foo/../../etc/passwd")
f.Add("..\\windows\\system32")
f.Add("foo\\..\\..\\windows")
// Encoded traversal attempts
f.Add("%2e%2e")
f.Add("%2e%2e%2f")
f.Add("..%2f")
f.Add("%2e.")
f.Add(".%2e")
f.Add("\x2e\x2e")
f.Add("\\x2e\\x2e")
// Unicode and special characters
f.Add("€test")
f.Add("test€")
f.Add("中文")
f.Add("🎉emoji")
f.Add("\x00null")
f.Add("test\x00null")
f.Add("\nnewline")
f.Add("test\ttab")
// Absolute paths
f.Add("/etc/passwd")
f.Add("C:\\Windows\\System32")
f.Add("\\\\server\\share")
f.Add("//server/share")
// Home directory
f.Add("~")
f.Add("~/config")
f.Add("~user/file")
// Hidden files
f.Add(".hidden")
f.Add("dir/.hidden")
f.Add(".git/config")
// Edge cases
f.Add("")
f.Add(" ")
f.Add(" ")
f.Add("\t")
f.Add(".")
f.Add("./")
f.Add("./file")
f.Add(".../")
f.Add("...")
f.Add("....")
// Very long names
f.Add(strings.Repeat("a", 255))
f.Add(strings.Repeat("a/", 100) + "file")
f.Add(strings.Repeat("../", 50) + "etc/passwd")
f.Fuzz(func(t *testing.T, input string) {
// The function should never panic
func() {
defer func() {
if r := recover(); r != nil {
t.Errorf("ValidateSheetName panicked with input %q: %v", input, r)
}
}()
err := ValidateSheetName(input)
// Security invariants that must always hold
if err == nil {
// If validation passed, verify security properties
// Should not contain ".." for path traversal
if strings.Contains(input, "..") {
t.Errorf("validation passed but input contains '..': %q", input)
}
// Should not be empty
if input == "" {
t.Error("validation passed for empty input")
}
// Should not start with ~ (home directory)
if strings.HasPrefix(input, "~") {
t.Errorf("validation passed but input starts with '~': %q", input)
}
// Base filename should not start with .
parts := strings.Split(input, "/")
if len(parts) > 0 {
lastPart := parts[len(parts)-1]
if strings.HasPrefix(lastPart, ".") && lastPart != "." {
t.Errorf("validation passed but filename starts with '.': %q", input)
}
}
// Additional check: result should be valid UTF-8
if !utf8.ValidString(input) {
// While the function doesn't explicitly check this,
// we want to ensure it handles invalid UTF-8 gracefully
t.Logf("validation passed for invalid UTF-8: %q", input)
}
}
}()
})
}
// FuzzValidateSheetNamePathTraversal specifically targets path traversal bypasses
func FuzzValidateSheetNamePathTraversal(f *testing.F) {
// Seed corpus focusing on path traversal variations
f.Add("..", "/", "")
f.Add("", "..", "/")
f.Add("a", "b", "c")
f.Fuzz(func(t *testing.T, prefix string, middle string, suffix string) {
// Construct various path traversal attempts
inputs := []string{
prefix + ".." + suffix,
prefix + "/.." + suffix,
prefix + "\\.." + suffix,
prefix + middle + ".." + suffix,
prefix + "../" + middle + suffix,
prefix + "..%2f" + suffix,
prefix + "%2e%2e" + suffix,
prefix + "%2e%2e%2f" + suffix,
}
for _, input := range inputs {
func() {
defer func() {
if r := recover(); r != nil {
t.Errorf("ValidateSheetName panicked with constructed input %q: %v", input, r)
}
}()
err := ValidateSheetName(input)
// If the input contains literal "..", it must be rejected
if strings.Contains(input, "..") && err == nil {
t.Errorf("validation incorrectly passed for input containing '..': %q", input)
}
}()
}
})
}