Files
brianmcgee 923b96db2e state: validate file names in Put/PutTombstone
`files.Put()` and `files.PutTombstone()` accept arbitrary `sig.Name` values without validation. This allows `../` paths to resolve outside the state directory.

The NixOS module/systemd service would have prevented this from being a real problem, but best to lock it down all the same.
2026-04-08 12:21:25 +01:00

75 lines
1.8 KiB
Go

package model
import (
"errors"
"fmt"
"io/fs"
"path/filepath"
"regexp"
"strings"
"git.clan.lol/clan/data-mesher/pkg/crypto"
)
var (
NameRegex = regexp.MustCompile(`^[a-z0-9_]{1,255}(/[a-z0-9_]{1,255})*$`)
NamespaceNameRegex = regexp.MustCompile(`^[a-z0-9_]{1,255}$`)
ErrInvalidName = errors.New("invalid name")
)
// ValidateName checks that a file name is either a valid static file name (matching NameRegex) or a valid namespace
// path ({namespace}/{url_encoded_key}).
// This rejects path traversal sequences and any name that does not conform to the expected formats.
func ValidateName(name string) error {
if name == "" {
return fmt.Errorf("%w: empty name", ErrInvalidName)
}
if strings.ContainsRune(name, 0) {
return fmt.Errorf("%w: contains null byte", ErrInvalidName)
}
if !fs.ValidPath(name) {
return fmt.Errorf("%w: %s", ErrInvalidName, name)
}
if !filepath.IsLocal(name) {
return fmt.Errorf("%w: %s", ErrInvalidName, name)
}
if NameRegex.MatchString(name) {
return nil
}
if _, _, err := ParseNamespacePath(name); err == nil {
return nil
}
return fmt.Errorf("%w: %s", ErrInvalidName, name)
}
func ValidateNamespaceName(name string) error {
if !NamespaceNameRegex.MatchString(name) {
return fmt.Errorf("invalid namespace name: %s", name)
}
return nil
}
// ParseNamespacePath checks if a file name matches the pattern {namespace}/{url_encoded_key}
// and returns the namespace and parsed public key.
func ParseNamespacePath(name string) (string, *crypto.PublicKey, error) {
parts := strings.SplitN(name, "/", 2)
if len(parts) != 2 {
return "", nil, fmt.Errorf("not a namespace path: %s", name)
}
signerKey, err := crypto.ParsePublicKeyURLEncoded(parts[1])
if err != nil {
return "", nil, fmt.Errorf("invalid signer key in namespace path: %w", err)
}
return parts[0], signerKey, nil
}