diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 390d375..6b2469f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -248,7 +248,28 @@ jobs: markdownlint-cli2 '**/*.md' # ------------------------------------------------------------------------- - # 9. Cross-platform build matrix — zero CGO, all targets. + # 9. Fuzz the message-sanitization and cache-key helpers that consume + # untrusted/adversarial LLM conversation content. + # ------------------------------------------------------------------------- + fuzz: + name: fuzz (60s) + runs-on: ubuntu-latest + needs: [test] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Run fuzz targets + run: | + go test -fuzz=FuzzSanitizeMessages -fuzztime=60s ./client + go test -fuzz=FuzzMergeConsecutiveRoles -fuzztime=60s ./client + go test -fuzz=FuzzBuildCacheKey -fuzztime=60s ./client + go test -fuzz=FuzzGuardrailsCheck -fuzztime=60s ./client + + # ------------------------------------------------------------------------- + # 10. Cross-platform build matrix — zero CGO, all targets. # ------------------------------------------------------------------------- build: name: build (${{ matrix.goos }}/${{ matrix.goarch }}) diff --git a/catalog/live/fetchers_providers.go b/catalog/live/fetchers_providers.go index b69855c..76d2a03 100644 --- a/catalog/live/fetchers_providers.go +++ b/catalog/live/fetchers_providers.go @@ -621,7 +621,7 @@ func FetchClinePass(env map[string]string) ([]Entry, error) { {"cline-pass/mimo-v2.5-pro", "MiMo V2.5 Pro", "xiaomi", 131072, 8192, 1.74, 3.48}, {"cline-pass/mimo-v2.5", "MiMo V2.5", "xiaomi", 131072, 8192, 0.14, 0.28}, {"cline-pass/qwen3.7-max", "Qwen 3.7 Max", "qwen", 131072, 8192, 2.50, 7.50}, - {"cline-pass/qwen3.7-plus", "Qwen 3.7 Plus", "qwen", 131072, 8192, 0.40, 1.60}, + {"cline-pass/qwen3.7-plus", "Qwen 3.7 Plus", "qwen", 131072, 8192, 0.40, 1.28}, {"cline-pass/poolside-laguna-m.1-free", "Poolside Laguna M.1 (Free)", "poolside", 262144, 32768, 0, 0}, } var entries []Entry diff --git a/catalog/registry/providers.go b/catalog/registry/providers.go index 98c3df1..8df4979 100644 --- a/catalog/registry/providers.go +++ b/catalog/registry/providers.go @@ -174,7 +174,7 @@ func providerSpecs() []ProviderSpec { BaseURLEnv: []string{"POOLSIDE_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"}, ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://inference.poolside.ai/v1", LiveFetcherKey: "poolside", LiveCatalogKey: "poolside", - ProtocolID: "openai-chat-completions", AdapterID: "poolside", RuntimeProfileKey: "poolside", + APIProtocolID: "openai-chat-completions", AdapterID: "poolside", RuntimeProfileKey: "poolside", }, { ProviderID: "groq", DisplayName: "Groq", DeploymentID: "groq-direct", SortOrder: 19, ChatPreference: 21, @@ -182,7 +182,11 @@ func providerSpecs() []ProviderSpec { BaseURLEnv: []string{"GROQ_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"}, ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.groq.com/openai/v1", LiveFetcherKey: "groq", LiveCatalogKey: "groq", +<<<<<<< HEAD + APIProtocolID: "openai-chat-completions", AdapterID: "groq", RuntimeProfileKey: "groq", +======= ProtocolID: "openai-chat-completions", AdapterID: "groq", RuntimeProfileKey: "groq", +>>>>>>> origin/main }, { ProviderID: "clinepass", DisplayName: "ClinePass", DeploymentID: "clinepass", SortOrder: 20, ChatPreference: 22, @@ -190,7 +194,11 @@ func providerSpecs() []ProviderSpec { BaseURLEnv: []string{"CLINE_API_BASE", "OPENAI_BASE_URL", "OPENAI_API_BASE"}, ProbeKind: ProbeNone, LiveFetcherKey: "clinepass", LiveCatalogKey: "clinepass", +<<<<<<< HEAD + APIProtocolID: "openai-chat-completions", AdapterID: "clinepass", RuntimeProfileKey: "clinepass", +======= ProtocolID: "openai-chat-completions", AdapterID: "clinepass", RuntimeProfileKey: "clinepass", +>>>>>>> origin/main }, { ProviderID: "opencodego", DisplayName: "OpenCode Go", DeploymentID: "opencodego", SortOrder: 21, ChatPreference: 13, diff --git a/catalog/xiaomi/platform.go b/catalog/xiaomi/platform.go index 7fb0472..b68bdb8 100644 --- a/catalog/xiaomi/platform.go +++ b/catalog/xiaomi/platform.go @@ -14,6 +14,11 @@ import ( // Inference GET {base}/models only returns id/object/owned_by. const DefaultPlatformModelsURL = "https://platform.xiaomimimo.com/api/v1/models" +// maxPlatformResponseBytes caps how much of the platform catalog response is +// read, so a malicious or misconfigured endpoint (including the env-overridable +// XIAOMI_MIMO_PLATFORM_MODELS_URL) cannot exhaust memory via an unbounded body. +const maxPlatformResponseBytes = 10 * 1024 * 1024 // 10 MiB + var platformHTTPClient = &http.Client{Timeout: 30 * time.Second} // PlatformModel is one row from the platform catalog API. @@ -65,7 +70,7 @@ func FetchPlatformModelsIndex(ctx context.Context, catalogURL string) (map[strin return nil, fmt.Errorf("xiaomi platform catalog: %w", err) } defer func() { _ = resp.Body.Close() }() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, maxPlatformResponseBytes)) if err != nil { return nil, err }