Merge pull request #35 from s3rj1k/main

Add --insecure flag to skip TLS verification

Co-authored-by: s3rj1k <evasive.gyron@gmail.com>
This commit is contained in:
patriceckhart 2026-06-16 07:41:13 +02:00
commit e0ca3fdd3e
6 changed files with 138 additions and 0 deletions

View file

@ -74,6 +74,9 @@ type Args struct {
// skill discovery, including built-ins.
WithSkills bool
// InsecureTLS skips TLS verification for custom inference endpoints.
InsecureTLS bool
// NoYolo turns on per-tool confirmation. Before each tool
// invocation the TUI prompts the user with the tool name + args
// and waits for an explicit yes/no. The user can also pick
@ -190,6 +193,8 @@ func ParseArgs(in []string) (Args, error) {
case "--with-skills", "--with-skill":
// Deprecated no-op: user skills are loaded by default.
a.WithSkills = true
case "--insecure":
a.InsecureTLS = true
case "--no-yolo":
a.NoYolo = true
case "--reasoning":
@ -373,6 +378,7 @@ func PrintHelp(version string) {
row{"--model ID", "model id (see --list-models)"},
row{"--api-key KEY", "api key for this run (env / auth.json fallback)"},
row{"--base-url URL", "override provider api base url"},
row{"--insecure", "skip TLS certificate verification (for self-signed-cert endpoints)"},
row{"--reasoning off|minimum|low|medium|high|maximum", "set thinking level on supported models"},
row{"--temperature N", "sampling temperature, 0 to 2 (omit for provider default)"},
)

View file

@ -420,6 +420,11 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
args.BaseURL = "http://localhost:11434"
}
provider.InsecureSkipVerify = (args.InsecureTLS || cfg.Insecure) && args.BaseURL != ""
if provider.InsecureSkipVerify {
provider.ApplyInsecureTLS()
}
// If the model has a base URL, credentials are optional (local
// models like ollama don't need real API keys).
if resolvedModel.BaseURL != "" && credErr != nil {

View file

@ -211,3 +211,62 @@ func TestCanonicalProviderAliasesAreKnown(t *testing.T) {
}
}
}
func TestResolveInsecureOnlyWithCustomBaseURL(t *testing.T) {
orig := provider.InsecureSkipVerify
t.Cleanup(func() { provider.InsecureSkipVerify = orig })
t.Setenv("ZOT_HOME", t.TempDir())
t.Setenv("OPENAI_API_KEY", "test-key")
// no --base-url: must stay false even with --insecure.
provider.InsecureSkipVerify = false
_, err := Resolve(Args{Provider: "openai", InsecureTLS: true}, false)
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if provider.InsecureSkipVerify {
t.Fatal("InsecureSkipVerify must not be set without a custom base URL")
}
// --base-url + --insecure: must be true.
provider.InsecureSkipVerify = false
_, err = Resolve(Args{Provider: "openai", InsecureTLS: true, BaseURL: "https://my-llm.internal/v1"}, false)
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if !provider.InsecureSkipVerify {
t.Fatal("InsecureSkipVerify must be set with --insecure and --base-url")
}
}
func TestResolveInsecureFromConfig(t *testing.T) {
orig := provider.InsecureSkipVerify
t.Cleanup(func() { provider.InsecureSkipVerify = orig })
t.Setenv("ZOT_HOME", t.TempDir())
t.Setenv("OPENAI_API_KEY", "test-key")
if err := SaveConfig(Config{Provider: "openai", Insecure: true}); err != nil {
t.Fatal(err)
}
// no --base-url: must stay false.
provider.InsecureSkipVerify = false
_, err := Resolve(Args{Provider: "openai"}, false)
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if provider.InsecureSkipVerify {
t.Fatal("InsecureSkipVerify must not be set without a custom base URL")
}
// --base-url: must be true.
provider.InsecureSkipVerify = false
_, err = Resolve(Args{Provider: "openai", BaseURL: "https://my-llm.internal/v1"}, false)
if err != nil {
t.Fatalf("Resolve: %v", err)
}
if !provider.InsecureSkipVerify {
t.Fatal("InsecureSkipVerify must be set when config insecure=true and --base-url is provided")
}
}

View file

@ -45,6 +45,9 @@ type Config struct {
// which is on; false shows ignored entries. Toggle from /settings.
RespectGitignore *bool `json:"respect_gitignore,omitempty"`
// Insecure skips TLS verification for custom inference endpoints.
Insecure bool `json:"insecure,omitempty"`
// LastChangelogShown is the version whose release-notes
// dialog the user has already seen. When the running binary's
// version differs, the next interactive run shows the

View file

@ -0,0 +1,16 @@
package provider
import (
"crypto/tls"
"net/http"
)
// InsecureSkipVerify disables TLS cert verification for inference connections.
var InsecureSkipVerify bool
// ApplyInsecureTLS replaces http.DefaultTransport to skip TLS cert verification.
func ApplyInsecureTLS() {
http.DefaultTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
}
}

View file

@ -0,0 +1,49 @@
package provider
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestApplyInsecureTLSSetsDefaultTransport(t *testing.T) {
orig := http.DefaultTransport
t.Cleanup(func() { http.DefaultTransport = orig })
ApplyInsecureTLS()
tr, ok := http.DefaultTransport.(*http.Transport)
if !ok {
t.Fatalf("expected *http.Transport, got %T", http.DefaultTransport)
}
if tr.TLSClientConfig == nil || !tr.TLSClientConfig.InsecureSkipVerify {
t.Fatal("expected InsecureSkipVerify=true in TLS config")
}
}
func TestInsecureClientReachesTLSServer(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
orig := http.DefaultTransport
t.Cleanup(func() { http.DefaultTransport = orig })
client := &http.Client{}
if _, err := client.Get(srv.URL); err == nil {
t.Fatal("expected TLS error with default transport, got nil")
}
ApplyInsecureTLS()
resp, err := client.Get(srv.URL)
if err != nil {
t.Fatalf("request failed after ApplyInsecureTLS: %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status=%d", resp.StatusCode)
}
}