From 2b2a6c6ccde0b1fbd4529ec5de9f30d3718483d8 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 9 Feb 2026 11:18:55 -0500 Subject: [PATCH] security: validate genesis JSON and keystore file permissions - Add json.Valid() check before passing genesis data to P-Chain transaction builder, preventing wasted fees on malformed input - Add file permission validation when loading keystore files, refusing to read keys with insecure permissions (e.g. 0644) and providing actionable fix instructions - Skip permission check on Windows where POSIX perms don't apply --- cmd/chain.go | 4 ++++ pkg/keystore/keystore.go | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/cmd/chain.go b/cmd/chain.go index 0b38883..a7d1788 100644 --- a/cmd/chain.go +++ b/cmd/chain.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "os" @@ -60,6 +61,9 @@ var chainCreateCmd = &cobra.Command{ if err != nil { return fmt.Errorf("failed to read genesis file: %w", err) } + if !json.Valid(genesis) { + return fmt.Errorf("genesis file contains invalid JSON") + } // Default to Subnet-EVM vmID := constants.SubnetEVMID diff --git a/pkg/keystore/keystore.go b/pkg/keystore/keystore.go index 2168ae7..b7d6f90 100644 --- a/pkg/keystore/keystore.go +++ b/pkg/keystore/keystore.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "strings" "time" @@ -32,6 +33,24 @@ const ( var keyNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`) +// validateFilePermissions checks that a file has the expected permissions. +// On Windows this is a no-op since POSIX permissions don't apply. +func validateFilePermissions(path string, expected os.FileMode) error { + if runtime.GOOS == "windows" { + return nil + } + info, err := os.Stat(path) + if err != nil { + return err + } + perm := info.Mode().Perm() + if perm != expected { + return fmt.Errorf("insecure permissions on %s: %04o (expected %04o). Fix with: chmod %04o %s", + path, perm, expected, expected, path) + } + return nil +} + // ValidateKeyName validates a key name for safe filesystem usage. func ValidateKeyName(name string) error { name = strings.TrimSpace(name) @@ -305,6 +324,9 @@ func (ks *KeyStore) LoadKey(name string, password []byte) ([]byte, error) { // Read key file keyPath := filepath.Join(ks.basePath, name+keyExtension) + if err := validateFilePermissions(keyPath, 0600); err != nil { + return nil, fmt.Errorf("keystore security check failed: %w", err) + } data, err := os.ReadFile(keyPath) if err != nil { return nil, fmt.Errorf("failed to read key file: %w", err)