diff --git a/cmd/keyspec.go b/cmd/keyspec.go index 3aeaca9..2c8b707 100644 --- a/cmd/keyspec.go +++ b/cmd/keyspec.go @@ -51,6 +51,9 @@ func ParseKey(raw string, defaults bool) (KeySpec, error) { if strings.TrimSpace(rawDB) == "" { return KeySpec{}, fmt.Errorf("bad key format, use KEY@DB") } + if err := validateDBName(rawDB); err != nil { + return KeySpec{}, err + } } key := strings.ToLower(rawKey) diff --git a/cmd/shared.go b/cmd/shared.go index aab444c..fdec7d9 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -178,6 +178,9 @@ func (s *Store) parseDB(v string, defaults bool) (string, error) { } return "", fmt.Errorf("cannot parse db: bad db format, use DB or @DB") } + if err := validateDBName(db); err != nil { + return "", fmt.Errorf("cannot parse db: %w", err) + } return strings.ToLower(db), nil } @@ -197,7 +200,11 @@ func (s *Store) path(args ...string) (string, error) { if err := os.MkdirAll(override, 0o750); err != nil { return "", err } - return filepath.Join(append([]string{override}, args...)...), nil + target := filepath.Join(append([]string{override}, args...)...) + if err := ensureSubpath(override, target); err != nil { + return "", err + } + return target, nil } scope := gap.NewVendorScope(gap.User, "pda", "stores") dir, err := scope.DataPath("") @@ -207,7 +214,11 @@ func (s *Store) path(args ...string) (string, error) { if err := os.MkdirAll(dir, 0o750); err != nil { return "", err } - return filepath.Join(append([]string{dir}, args...)...), nil + target := filepath.Join(append([]string{dir}, args...)...) + if err := ensureSubpath(dir, target); err != nil { + return "", err + } + return target, nil } func (s *Store) suggestStores(target string) ([]string, error) { @@ -229,6 +240,33 @@ func (s *Store) suggestStores(target string) ([]string, error) { return suggestions, nil } +func ensureSubpath(base, target string) error { + absBase, err := filepath.Abs(base) + if err != nil { + return err + } + absTarget, err := filepath.Abs(target) + if err != nil { + return err + } + rel, err := filepath.Rel(absBase, absTarget) + if err != nil { + return err + } + sep := string(filepath.Separator) + if rel == ".." || strings.HasPrefix(rel, ".."+sep) { + return fmt.Errorf("path escapes store root") + } + return nil +} + +func validateDBName(name string) error { + if strings.ContainsAny(name, `/\`) { + return fmt.Errorf("bad db format, use DB or @DB") + } + return nil +} + func formatExpiry(expiresAt uint64) string { if expiresAt == 0 { return "never" diff --git a/main.go b/main.go index 0f85325..944837e 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + package main import "github.com/llywelwyn/pda/cmd" diff --git a/main_test.go b/main_test.go index 7f977cb..78a497b 100644 --- a/main_test.go +++ b/main_test.go @@ -19,6 +19,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + package main import (