From b5946b34b8c7a90d7d5d0a054b5d25c1ebb92a07 Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 29 Jan 2026 10:24:18 +0800 Subject: [PATCH] refactor(proxmox): add struct level validation for node configuration services and files Add Validate() method to NodeConfig that implements the CustomValidator interface. The method checks all services and files for invalid shell metacharacters (&, $(), etc.) to prevent shell injection attacks. Testing: Added validation_test.go with 6 table-driven test cases covering valid inputs and various shell metacharacter injection attempts. --- internal/proxmox/node.go | 17 +++++++++ internal/proxmox/validation_test.go | 55 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 internal/proxmox/validation_test.go diff --git a/internal/proxmox/node.go b/internal/proxmox/node.go index ff6f7c78..11cacc75 100644 --- a/internal/proxmox/node.go +++ b/internal/proxmox/node.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + gperr "github.com/yusing/goutils/errs" "github.com/yusing/goutils/pool" ) @@ -25,6 +26,22 @@ type Node struct { // statsScriptInitErrs *xsync.Map[int, error] } +// Validate implements the serialization.CustomValidator interface. +func (n *NodeConfig) Validate() gperr.Error { + var errs gperr.Builder + for i, service := range n.Services { + if err := checkValidInput(service); err != nil { + errs.AddSubjectf(err, "services[%d]", i) + } + } + for i, file := range n.Files { + if err := checkValidInput(file); err != nil { + errs.AddSubjectf(err, "files[%d]", i) + } + } + return errs.Error() +} + var Nodes = pool.New[*Node]("proxmox_nodes") func NewNode(client *Client, name, id string) *Node { diff --git a/internal/proxmox/validation_test.go b/internal/proxmox/validation_test.go new file mode 100644 index 00000000..c44086c4 --- /dev/null +++ b/internal/proxmox/validation_test.go @@ -0,0 +1,55 @@ +package proxmox + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/yusing/godoxy/internal/serialization" +) + +func TestValidateCommandArgs(t *testing.T) { + tests := []struct { + name string + yamlCfg string + wantErr bool + }{ + { + name: "valid_services", + yamlCfg: `services: ["foo", "bar"]`, + wantErr: false, + }, + { + name: "invalid_services", + yamlCfg: `services: ["foo", "bar & baz"]`, + wantErr: true, + }, + { + name: "invalid_services_with_$(", + yamlCfg: `services: ["foo", "bar & $(echo 'hello')"]`, + wantErr: true, + }, + { + name: "valid_files", + yamlCfg: `files: ["foo", "bar"]`, + wantErr: false, + }, + { + name: "invalid_files", + yamlCfg: `files: ["foo", "bar & baz"]`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var cfg NodeConfig + err := serialization.UnmarshalValidateYAML([]byte(tt.yamlCfg), &cfg) + if tt.wantErr { + require.Error(t, err) + require.ErrorContains(t, err, "input contains invalid characters") + } else { + require.NoError(t, err) + } + }) + } +}