Skip to content

Commit 0954300

Browse files
authored
Merge pull request #1078 from jeanlaurent/RunSecretProviderFix
Fix RunSecretProvider path issue
2 parents ac8bd5c + 42fdc76 commit 0954300

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

pkg/environment/secrets.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import (
44
"context"
55
"log/slog"
66
"os"
7-
"path/filepath"
87
"strings"
8+
9+
"github.com/docker/cagent/pkg/path"
910
)
1011

1112
type RunSecretsProvider struct {
@@ -19,9 +20,14 @@ func NewRunSecretsProvider() *RunSecretsProvider {
1920
}
2021

2122
func (p *RunSecretsProvider) Get(_ context.Context, name string) string {
22-
path := filepath.Join(p.root, name)
23+
// Validate the secret name to prevent path traversal
24+
validatedPath, err := path.ValidatePathInDirectory(name, p.root)
25+
if err != nil {
26+
slog.Debug("Invalid secret name", "name", name, "error", err)
27+
return ""
28+
}
2329

24-
buf, err := os.ReadFile(path)
30+
buf, err := os.ReadFile(validatedPath)
2531
if err != nil {
2632
if os.IsNotExist(err) {
2733
return ""

pkg/environment/secrets_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,57 @@ func TestRunSecretsProvider(t *testing.T) {
7171
})
7272
}
7373
}
74+
75+
func TestRunSecretsProvider_PathTraversal(t *testing.T) {
76+
t.Parallel()
77+
78+
// Create a temporary directory structure
79+
tmpDir := t.TempDir()
80+
81+
// Create a "secrets" subdirectory
82+
secretsDir := filepath.Join(tmpDir, "secrets")
83+
require.NoError(t, os.Mkdir(secretsDir, 0o755))
84+
85+
// Create a legitimate secret inside the secrets directory
86+
secretFile := filepath.Join(secretsDir, "api_key")
87+
require.NoError(t, os.WriteFile(secretFile, []byte("SECRET_VALUE"), 0o644))
88+
89+
// Create a sensitive file OUTSIDE the secrets directory (simulating /etc/passwd, etc.)
90+
sensitiveFile := filepath.Join(tmpDir, "sensitive.txt")
91+
require.NoError(t, os.WriteFile(sensitiveFile, []byte("SENSITIVE_DATA"), 0o644))
92+
93+
provider := &RunSecretsProvider{
94+
root: secretsDir,
95+
}
96+
97+
// Test 1: Normal access should work
98+
result := provider.Get(t.Context(), "api_key")
99+
assert.Equal(t, "SECRET_VALUE", result, "Normal secret access should work")
100+
101+
// Test 2: Path traversal with ../ should be blocked
102+
result = provider.Get(t.Context(), "../sensitive.txt")
103+
assert.Empty(t, result, "Path traversal with ../ should be blocked and return empty string")
104+
105+
// Test 3: Multiple path traversal levels
106+
result = provider.Get(t.Context(), "../../sensitive.txt")
107+
assert.Empty(t, result, "Multiple path traversal levels should be blocked")
108+
109+
// Test 4: Absolute path outside secrets dir should be blocked
110+
result = provider.Get(t.Context(), sensitiveFile)
111+
assert.Empty(t, result, "Absolute path outside secrets dir should be blocked")
112+
113+
// Test 5: Path with ../ that normalizes to valid path within directory
114+
// This is NOT a vulnerability - it's proper path normalization
115+
subDir := filepath.Join(secretsDir, "subdir")
116+
require.NoError(t, os.Mkdir(subDir, 0o755))
117+
subSecret := filepath.Join(subDir, "subsecret")
118+
require.NoError(t, os.WriteFile(subSecret, []byte("SUB_VALUE"), 0o644))
119+
120+
// subdir/../api_key normalizes to api_key, which is valid
121+
result = provider.Get(t.Context(), "subdir/../api_key")
122+
assert.Equal(t, "SECRET_VALUE", result, "Path that normalizes to valid location should work")
123+
124+
// But accessing subsecret directly should work
125+
result = provider.Get(t.Context(), "subdir/subsecret")
126+
assert.Equal(t, "SUB_VALUE", result, "Subdirectory access should work")
127+
}

0 commit comments

Comments
 (0)