5d115fc4b7
---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.
59 lines
2.0 KiB
Go
59 lines
2.0 KiB
Go
// Package ns: validate.go implements namespace name validation
|
|
// (REQ-120, F4 — path traversal hardening). A namespace name is used
|
|
// to construct a filesystem path via filepath.Join(Root(), name); a
|
|
// name containing "..", "/", or shell-relevant characters could
|
|
// traverse outside ORCA_HOME or inject into SSH commands. ValidateName
|
|
// rejects any name that is not safe for both path construction and
|
|
// shell interpolation.
|
|
package ns
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// ValidateName returns an error if the namespace name is not safe for
|
|
// filesystem path construction or shell interpolation. A safe name:
|
|
// - is non-empty and at most 128 characters;
|
|
// - contains only printable, non-space runes;
|
|
// - does not contain "/", "\", "..", a leading "-", null bytes, or
|
|
// any control character;
|
|
// - is not a reserved name ("cluster", "_defaults").
|
|
//
|
|
// The reserved-name check here is defensive; the CLI also enforces it.
|
|
// ValidateName is the single choke-point for any code path that
|
|
// converts a user-supplied namespace name into a path or a shell token.
|
|
func ValidateName(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("namespace name is empty")
|
|
}
|
|
if len(name) > 128 {
|
|
return fmt.Errorf("namespace name %q exceeds 128 characters", name)
|
|
}
|
|
if name == "cluster" {
|
|
return fmt.Errorf("name %q is reserved for the cluster-wide dir", name)
|
|
}
|
|
if strings.Contains(name, "..") {
|
|
return fmt.Errorf("namespace name %q contains \"..\" (path traversal)", name)
|
|
}
|
|
if strings.ContainsAny(name, `/\`) {
|
|
return fmt.Errorf("namespace name %q contains a path separator", name)
|
|
}
|
|
if strings.HasPrefix(name, "-") {
|
|
return fmt.Errorf("namespace name %q starts with '-' (shell flag injection)", name)
|
|
}
|
|
for _, r := range name {
|
|
if r == 0 {
|
|
return fmt.Errorf("namespace name %q contains a null byte", name)
|
|
}
|
|
if unicode.IsControl(r) {
|
|
return fmt.Errorf("namespace name %q contains a control character", name)
|
|
}
|
|
if unicode.IsSpace(r) {
|
|
return fmt.Errorf("namespace name %q contains a space", name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|