diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 870a2427e..71f1f5cce 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -121,14 +121,10 @@ func (c *cache) Store(question, response string) { key := c.normalize(question) c.mu.Lock() + defer c.mu.Unlock() c.entries[key] = response - var snapshot map[string]string - if c.persist != nil { - snapshot = maps.Clone(c.entries) - } - c.mu.Unlock() - if c.persist != nil { + snapshot := maps.Clone(c.entries) c.persist(snapshot) } } @@ -220,6 +216,6 @@ func syncDir(dir string) { if err != nil { return } + defer d.Close() _ = d.Sync() - _ = d.Close() } diff --git a/pkg/teamloader/teamloader.go b/pkg/teamloader/teamloader.go index 9587c2150..ac866af00 100644 --- a/pkg/teamloader/teamloader.go +++ b/pkg/teamloader/teamloader.go @@ -174,6 +174,12 @@ func LoadWithConfig(ctx context.Context, agentSource config.Source, runConfig *c cachePath := agentConfig.Cache.Path if cachePath != "" && !filepath.IsAbs(cachePath) { cachePath = filepath.Join(parentDir, cachePath) + cachePath = filepath.Clean(cachePath) + // Ensure the resolved path is within parentDir to prevent path traversal + cleanParent := filepath.Clean(parentDir) + string(filepath.Separator) + if !strings.HasPrefix(cachePath+string(filepath.Separator), cleanParent) { + return nil, fmt.Errorf("agent %q: cache path %q escapes parent directory", agentConfig.Name, agentConfig.Cache.Path) + } } c, err := cache.New(cache.Config{ Enabled: true, diff --git a/pkg/teamloader/teamloader_test.go b/pkg/teamloader/teamloader_test.go index 4d1882521..b7fa61ab9 100644 --- a/pkg/teamloader/teamloader_test.go +++ b/pkg/teamloader/teamloader_test.go @@ -483,3 +483,29 @@ func TestExternalDepthContext(t *testing.T) { ctx = contextWithExternalDepth(ctx, 7) assert.Equal(t, 7, externalDepthFromContext(ctx)) } + +func TestLoadWithConfig_CachePathTraversal(t *testing.T) { + tmpDir := t.TempDir() + + // Create a config file with a path traversal attempt + configPath := filepath.Join(tmpDir, "agent.yaml") + configContent := ` +agents: + root: + model: openai/gpt-4o + description: Test agent + instruction: You are a test agent. + cache: + enabled: true + path: ../../../../etc/passwd +` + require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o600)) + + source := config.NewFileSource(configPath) + runConfig := &config.RuntimeConfig{} + runConfig.WorkingDir = tmpDir + + _, err := LoadWithConfig(t.Context(), source, runConfig) + require.Error(t, err) + assert.Contains(t, err.Error(), "escapes parent directory") +}