fix: support keyless Ollama and harden exec working_dir checks

This commit is contained in:
Kevin Perez 2026-02-18 15:31:32 +04:00
parent 80c8b57533
commit 9f6b5a73a5
No known key found for this signature in database
3 changed files with 65 additions and 13 deletions

View file

@ -33,6 +33,7 @@ type providerSelection struct {
workspace string
connectMode string
enableWebSearch bool
allowEmptyAPIKey bool
}
func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
@ -172,6 +173,14 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
sel.model = "deepseek-chat"
}
}
case "ollama":
sel.apiKey = cfg.Providers.Ollama.APIKey
sel.apiBase = cfg.Providers.Ollama.APIBase
sel.proxy = cfg.Providers.Ollama.Proxy
if sel.apiBase == "" {
sel.apiBase = "http://localhost:11434/v1"
}
sel.allowEmptyAPIKey = true
case "github_copilot", "copilot":
sel.providerType = providerTypeGitHubCopilot
if cfg.Providers.GitHubCopilot.APIBase != "" {
@ -268,13 +277,14 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
if sel.apiBase == "" {
sel.apiBase = "https://integrate.api.nvidia.com/v1"
}
case (strings.Contains(lowerModel, "ollama") || strings.HasPrefix(model, "ollama/")) && cfg.Providers.Ollama.APIKey != "":
case (strings.Contains(lowerModel, "ollama") || strings.HasPrefix(model, "ollama/")) && (cfg.Providers.Ollama.APIBase != "" || cfg.Providers.Ollama.APIKey != ""):
sel.apiKey = cfg.Providers.Ollama.APIKey
sel.apiBase = cfg.Providers.Ollama.APIBase
sel.proxy = cfg.Providers.Ollama.Proxy
if sel.apiBase == "" {
sel.apiBase = "http://localhost:11434/v1"
}
sel.allowEmptyAPIKey = true
case cfg.Providers.VLLM.APIBase != "":
sel.apiKey = cfg.Providers.VLLM.APIKey
sel.apiBase = cfg.Providers.VLLM.APIBase
@ -295,7 +305,7 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
}
if sel.providerType == providerTypeHTTPCompat {
if sel.apiKey == "" && !strings.HasPrefix(model, "bedrock/") {
if sel.apiKey == "" && !strings.HasPrefix(model, "bedrock/") && !sel.allowEmptyAPIKey {
return providerSelection{}, fmt.Errorf("no API key configured for provider (model: %s)", model)
}
if sel.apiBase == "" {

View file

@ -135,6 +135,24 @@ func TestResolveProviderSelection(t *testing.T) {
wantType: providerTypeHTTPCompat,
wantAPIBase: "http://localhost:11434/v1",
},
{
name: "explicit ollama provider allows empty key and uses default base",
setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Provider = "ollama"
cfg.Agents.Defaults.Model = "qwen2.5:14b"
},
wantType: providerTypeHTTPCompat,
wantAPIBase: "http://localhost:11434/v1",
},
{
name: "ollama model allows api base without key",
setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Model = "ollama/qwen2.5:14b"
cfg.Providers.Ollama.APIBase = "http://localhost:11434/v1"
},
wantType: providerTypeHTTPCompat,
wantAPIBase: "http://localhost:11434/v1",
},
{
name: "moonshot model keeps proxy and default base",
setup: func(cfg *config.Config) {

View file

@ -142,7 +142,8 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]any) *ToolResult
return ErrorResult("command is required")
}
cwd := t.workingDir
workspaceRoot := t.workingDir
cwd := workspaceRoot
if wd, ok := args["working_dir"].(string); ok && wd != "" {
if t.restrictToWorkspace && t.workingDir != "" {
resolvedWD, err := validatePath(wd, t.workingDir, true)
@ -150,6 +151,10 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]any) *ToolResult
return ErrorResult("Command blocked by safety guard (" + err.Error() + ")")
}
cwd = resolvedWD
} else if filepath.IsAbs(wd) {
cwd = wd
} else if workspaceRoot != "" {
cwd = filepath.Join(workspaceRoot, wd)
} else {
cwd = wd
}
@ -162,6 +167,20 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]any) *ToolResult
}
}
if absCwd, err := filepath.Abs(cwd); err == nil {
cwd = absCwd
}
if t.restrictToWorkspace && workspaceRoot != "" {
absWorkspace, err := filepath.Abs(workspaceRoot)
if err == nil {
rel, err := filepath.Rel(absWorkspace, cwd)
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return ErrorResult("working_dir must be within workspace")
}
}
}
if guardError := t.guardCommand(command, cwd); guardError != "" {
return ErrorResult(guardError)
}
@ -285,7 +304,12 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
return "Command blocked by safety guard (path traversal detected)"
}
cwdPath, err := filepath.Abs(cwd)
basePath := cwd
if t.workingDir != "" {
basePath = t.workingDir
}
basePath, err := filepath.Abs(basePath)
if err != nil {
return ""
}
@ -299,7 +323,7 @@ func (t *ExecTool) guardCommand(command, cwd string) string {
continue
}
rel, err := filepath.Rel(cwdPath, p)
rel, err := filepath.Rel(basePath, p)
if err != nil {
continue
}