Skip to content

Commit 9e9d187

Browse files
committed
fix: gosec errors
1 parent 21ecaba commit 9e9d187

3 files changed

Lines changed: 114 additions & 1 deletion

File tree

config/config.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"os"
8+
"path/filepath"
89
"strings"
910
"time"
1011

@@ -115,8 +116,40 @@ func Load() (*Config, error) {
115116
return cfg, nil
116117
}
117118

119+
// validateConfigPath validates that the config file path is safe
120+
func validateConfigPath(path string) error {
121+
if path == "" {
122+
return errors.New("config file path cannot be empty")
123+
}
124+
125+
// Clean the path to resolve any .. sequences
126+
cleanPath := filepath.Clean(path)
127+
128+
// Check for path traversal attempts
129+
if strings.Contains(cleanPath, "..") {
130+
return errors.New("config file path contains invalid path traversal sequences")
131+
}
132+
133+
// Ensure it's a JSON file
134+
if !strings.HasSuffix(strings.ToLower(cleanPath), ".json") {
135+
return errors.New("config file must have .json extension")
136+
}
137+
138+
// Check that the file exists and is readable
139+
if _, err := os.Stat(cleanPath); err != nil {
140+
return fmt.Errorf("config file not accessible: %w", err)
141+
}
142+
143+
return nil
144+
}
145+
118146
// LoadFromFile loads configuration from a JSON file
119147
func LoadFromFile(path string) (*Config, error) {
148+
// Validate the path for security
149+
if err := validateConfigPath(path); err != nil {
150+
return nil, fmt.Errorf("invalid config file path: %w", err)
151+
}
152+
120153
data, err := os.ReadFile(path)
121154
if err != nil {
122155
return nil, fmt.Errorf("failed to read config file %s: %w", path, err)

config/config_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,72 @@ func TestSecrets(t *testing.T) {
198198
value = store.GetWithDefault(ctx, testKey, defaultValue)
199199
assert.Equal(t, testValue, value)
200200
}
201+
202+
func TestValidateConfigPath(t *testing.T) {
203+
tests := []struct {
204+
name string
205+
path string
206+
expectError bool
207+
setup func() string // returns path to cleanup
208+
}{
209+
{
210+
name: "valid json file",
211+
path: "config_test.json",
212+
expectError: false,
213+
setup: func() string {
214+
tmpFile, _ := os.CreateTemp("", "config_test_*.json")
215+
tmpFile.WriteString("{}")
216+
tmpFile.Close()
217+
return tmpFile.Name()
218+
},
219+
},
220+
{
221+
name: "empty path",
222+
path: "",
223+
expectError: true,
224+
setup: func() string { return "" },
225+
},
226+
{
227+
name: "path traversal",
228+
path: "../../../etc/passwd",
229+
expectError: true,
230+
setup: func() string { return "" },
231+
},
232+
{
233+
name: "non-json file",
234+
path: "config.txt",
235+
expectError: true,
236+
setup: func() string {
237+
tmpFile, _ := os.CreateTemp("", "config_test_*.txt")
238+
tmpFile.WriteString("{}")
239+
tmpFile.Close()
240+
return tmpFile.Name()
241+
},
242+
},
243+
{
244+
name: "nonexistent file",
245+
path: "nonexistent.json",
246+
expectError: true,
247+
setup: func() string { return "" },
248+
},
249+
}
250+
251+
for _, tt := range tests {
252+
t.Run(tt.name, func(t *testing.T) {
253+
cleanupPath := tt.setup()
254+
if cleanupPath != "" {
255+
defer os.Remove(cleanupPath)
256+
if tt.path == "config_test.json" || tt.path == "config.txt" {
257+
tt.path = cleanupPath
258+
}
259+
}
260+
261+
err := validateConfigPath(tt.path)
262+
if tt.expectError {
263+
assert.Error(t, err)
264+
} else {
265+
assert.NoError(t, err)
266+
}
267+
})
268+
}
269+
}

leaderboard/skiplist.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package leaderboard
22

33
import (
4+
cryptorand "crypto/rand"
5+
"encoding/binary"
46
"math/rand/v2"
57
"sync"
68

@@ -26,11 +28,20 @@ type SkipList struct {
2628
}
2729

2830
func NewSkipList() *SkipList {
31+
// Use crypto/rand to generate a secure seed for PCG
32+
var seed [16]byte
33+
if _, err := cryptorand.Read(seed[:]); err != nil {
34+
// Fallback to zero seed if crypto/rand fails (extremely unlikely)
35+
seed = [16]byte{}
36+
}
37+
seed1 := binary.BigEndian.Uint64(seed[:8])
38+
seed2 := binary.BigEndian.Uint64(seed[8:])
39+
2940
return &SkipList{
3041
head: &node{},
3142
lvl: 1,
3243
byUser: map[core.UserID]*node{},
33-
rng: rand.New(rand.NewPCG(0, 0)),
44+
rng: rand.New(rand.NewPCG(seed1, seed2)),
3445
}
3546
}
3647

0 commit comments

Comments
 (0)