Files
orca/internal/ns/validate.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

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
}