Skip to content

Commit 771e186

Browse files
authored
Merge pull request #2 from armanfeyzi/af/add-gosec-sast
Add gosec SAST to CI and harden credentials file loading.
2 parents 81f344d + cfcd8dd commit 771e186

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ jobs:
3939
with:
4040
version: v2.12
4141

42+
- name: SAST (gosec)
43+
uses: securego/gosec@master
44+
with:
45+
args: -severity high -confidence high ./...
46+
4247
- name: Build Linux amd64 binary
4348
run: |
4449
mkdir -p dist

internal/config/config.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package config
22

33
import (
4+
"errors"
45
"log/slog"
56
"os"
7+
"path/filepath"
68
"strconv"
79
"strings"
810
"time"
@@ -132,10 +134,41 @@ func getEnvFloat32(key string, fallback float32) float32 {
132134
return float32(f)
133135
}
134136

137+
func validateCredentialsFilePath(path string) (string, error) {
138+
path = strings.TrimSpace(path)
139+
if path == "" {
140+
return "", errors.New("empty credentials file path")
141+
}
142+
if strings.Contains(path, "..") {
143+
return "", errors.New("credentials file path must not contain parent segments")
144+
}
145+
clean := filepath.Clean(path)
146+
if !filepath.IsAbs(clean) {
147+
return "", errors.New("credentials file path must be absolute")
148+
}
149+
return clean, nil
150+
}
151+
135152
func loadRegistryCredentialsFromEnv() map[string]registryauth.Credential {
136-
filePath := strings.TrimSpace(os.Getenv("HELM_WATCH_REGISTRY_CREDENTIALS_FILE"))
137-
if filePath != "" {
138-
data, err := os.ReadFile(filePath)
153+
rawPath := strings.TrimSpace(os.Getenv("HELM_WATCH_REGISTRY_CREDENTIALS_FILE"))
154+
if rawPath != "" {
155+
filePath, err := validateCredentialsFilePath(rawPath)
156+
if err != nil {
157+
slog.Warn("registry credentials file path invalid", "path", rawPath, "error", err)
158+
return nil
159+
}
160+
dir, name := filepath.Split(filePath)
161+
if name == "" || name == "." || name == ".." {
162+
slog.Warn("registry credentials file path invalid", "path", filePath, "error", "invalid file name")
163+
return nil
164+
}
165+
root, err := os.OpenRoot(dir)
166+
if err != nil {
167+
slog.Warn("registry credentials file not loaded", "path", filePath, "error", err)
168+
return nil
169+
}
170+
defer func() { _ = root.Close() }()
171+
data, err := root.ReadFile(name)
139172
if err != nil {
140173
slog.Warn("registry credentials file not loaded", "path", filePath, "error", err)
141174
return nil

internal/config/config_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package config
2+
3+
import "testing"
4+
5+
func TestValidateCredentialsFilePath(t *testing.T) {
6+
t.Parallel()
7+
8+
tests := []struct {
9+
name string
10+
path string
11+
want string
12+
wantErr bool
13+
}{
14+
{name: "absolute ok", path: "/var/run/secrets/creds.json", want: "/var/run/secrets/creds.json"},
15+
{name: "empty", path: "", wantErr: true},
16+
{name: "relative", path: "creds.json", wantErr: true},
17+
{name: "parent segment", path: "/var/run/../etc/passwd", wantErr: true},
18+
{name: "whitespace trimmed", path: " /etc/creds.json ", want: "/etc/creds.json"},
19+
}
20+
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
t.Parallel()
24+
got, err := validateCredentialsFilePath(tt.path)
25+
if tt.wantErr {
26+
if err == nil {
27+
t.Fatalf("validateCredentialsFilePath(%q) = %q, want error", tt.path, got)
28+
}
29+
return
30+
}
31+
if err != nil {
32+
t.Fatalf("validateCredentialsFilePath(%q): %v", tt.path, err)
33+
}
34+
if got != tt.want {
35+
t.Fatalf("validateCredentialsFilePath(%q) = %q, want %q", tt.path, got, tt.want)
36+
}
37+
})
38+
}
39+
}

0 commit comments

Comments
 (0)