Files
orca/internal/ns/validate_test.go
T
Jon Chery 5d115fc4b7 fix(P02): namespace path traversal (REQ-120, F4)
---ci---
project: orca
phase: 2
milestone: v0.12
status: execute
---/ci---

Add ns.ValidateName rejecting .., /, \, leading -, null bytes,
control chars, spaces, >128 chars, and reserved 'cluster'. Wire into
ns create/delete/inspect/validate/inherit/set-constraint + --parent
flag. Fuzz test + 14 traversal regression tests. No namespace dir can
escape ORCA_HOME.
2026-08-07 10:55:18 +00:00

101 lines
2.3 KiB
Go

package ns
import (
"testing"
)
// TestValidateName_Acceptable verifies normal names pass.
func TestValidateName_Acceptable(t *testing.T) {
ok := []string{
"prod",
"dev",
"team_a",
"team-b",
"ns1",
"a.b.c",
"0",
"with-dashes-and_underscores.and.dots",
"CAPS",
}
for _, name := range ok {
t.Run(name, func(t *testing.T) {
if err := ValidateName(name); err != nil {
t.Errorf("ValidateName(%q) = %v, want nil", name, err)
}
})
}
}
// TestValidateName_Rejected verifies traversal/injection names fail.
func TestValidateName_Rejected(t *testing.T) {
bad := []string{
"",
"..",
"../etc",
"foo/../bar",
"/etc",
"etc/",
"foo/bar",
"foo\\bar",
"-x",
"--flag",
"cluster",
"_defaults", // reserved names: cluster enforced here; _defaults
// is intentionally NOT rejected by ValidateName (it's the
// implicit root; the CLI prevents creating it). We accept it
// in the validator and let the CLI enforce the create rule.
"a\x00b",
"with space",
"tab\there",
"newline\nname",
}
for _, name := range bad {
t.Run(name, func(t *testing.T) {
// _defaults is a special case: it's a reserved name but
// ValidateName does NOT reject it (only "cluster" is
// rejected at this layer; _defaults is the implicit root).
if name == "_defaults" {
if err := ValidateName(name); err != nil {
t.Errorf("ValidateName(%q) should pass (implicit root)", name)
}
return
}
if err := ValidateName(name); err == nil {
t.Errorf("ValidateName(%q) = nil, want error", name)
}
})
}
}
// TestValidateName_Length verifies the 128-char limit.
func TestValidateName_Length(t *testing.T) {
long := make([]byte, 129)
for i := range long {
long[i] = 'a'
}
if err := ValidateName(string(long)); err == nil {
t.Error("129-char name should be rejected")
}
exact := make([]byte, 128)
for i := range exact {
exact[i] = 'a'
}
if err := ValidateName(string(exact)); err != nil {
t.Errorf("128-char name should pass: %v", err)
}
}
// FuzzValidateName is a fuzz test ensuring ValidateName never panics
// and rejects any name containing "..", "/", or control chars.
func FuzzValidateName(f *testing.F) {
f.Add("prod")
f.Add("..")
f.Add("/etc")
f.Add("-flag")
f.Add("a\x00b")
f.Fuzz(func(t *testing.T, name string) {
// ValidateName must never panic.
_ = ValidateName(name)
})
}