mirror of
https://github.com/cheat/cheat.git
synced 2026-03-07 03:03:32 +01:00
- Remove unused parameters, dead files, and inaccurate doc.go files - Extract shared helpers, eliminate duplication - Rename cheatpath.Cheatpath to cheatpath.Path - Optimize filesystem walks (WalkDir, skip .git) - Move sheet name validation to sheet.Validate - Move integration tests to test/integration/ - Consolidate internal/mock into mocks/ - Move fuzz.sh to test/ - Inline loadSheets helper into command callers - Extract config.New into its own file - Fix stale references in HACKING.md and CLAUDE.md - Restore plan9 build target - Remove redundant and low-value tests - Clean up project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
170 lines
3.9 KiB
Go
170 lines
3.9 KiB
Go
package sheet
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// FuzzValidate tests the Validate function with fuzzing
|
|
// to ensure it properly prevents path traversal and other security issues
|
|
func FuzzValidate(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("Validate panicked with input %q: %v", input, r)
|
|
}
|
|
}()
|
|
|
|
err := Validate(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)
|
|
}
|
|
}
|
|
}()
|
|
})
|
|
}
|
|
|
|
// FuzzValidatePathTraversal specifically targets path traversal bypasses
|
|
func FuzzValidatePathTraversal(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("Validate panicked with constructed input %q: %v", input, r)
|
|
}
|
|
}()
|
|
|
|
err := Validate(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)
|
|
}
|
|
}()
|
|
}
|
|
})
|
|
}
|