From e6bd7c2462e76991a1869b3eb6c88531b8145e11 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 a7d0ca26..f24d8f14 100644 --- a/internal/proxmox/node.go +++ b/internal/proxmox/node.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/bytedance/sonic" + 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) + } + }) + } +}