Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bakelens_crust.json
T

1 line
75 KiB
JSON

{"repo": "bakelens/crust", "files": {"internal/httpproxy/providers.go": {"f4a47e2cf2822196275075ab9a2e258b6fa9be8b": {"t": 1785746054.5573316, "n": 4258, "h": "eb5aeb2725f71f5b", "c": "package httpproxy\n\nimport (\n\t\"maps\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/BakeLens/crust/internal/config\"\n)\n\n// builtinProviders maps model keyword to provider config.\n// Matching logic: slash-split first (e.g. \"openai/gpt-4o\" → \"openai\"),\n// then combined prefix + hyphen-segment matching (longest wins).\nvar builtinProviders = map[string]config.ProviderConfig{\n\t\"claude\": {URL: \"https://api.anthropic.com\"},\n\t\"gpt\": {URL: \"https://api.openai.com\"},\n\t\"o1\": {URL: \"https://api.openai.com\"},\n\t\"o3\": {URL: \"https://api.openai.com\"},\n\t\"o4\": {URL: \"https://api.openai.com\"},\n\t\"openai\": {URL: \"https://api.openai.com\"},\n\t\"codex\": {URL: \"https://chatgpt.com/backend-api/codex\"},\n\t\"openai-codex\": {URL: \"https://chatgpt.com/backend-api/codex\"},\n\t\"deepseek\": {URL: \"https://api.deepseek.com\"},\n\t\"qwen\": {URL: \"https://dashscope.aliyuncs.com/compatible-mode\"},\n\t\"moonshot\": {URL: \"https://api.moonshot.ai\"},\n\t\"kimi\": {URL: \"https://api.moonshot.ai\"},\n\t// Gemini's OpenAI-compatible endpoint is at /v1beta/openai/, not /v1/.\n\t// With the default URL below, clients sending /v1/chat/completions get\n\t// /v1/chat/completions which returns 404. Users must override this in\n\t// config with the full base URL including the path prefix:\n\t// gemini: https://generativelanguage.googleapis.com/v1beta/openai\n\t\"gemini\": {URL: \"https://generativelanguage.googleapis.com/v1beta/openai\"},\n\t\"glm\": {URL: \"https://open.bigmodel.cn/api/paas/v4\"},\n\t\"mistral\": {URL: \"https://api.mistral.ai\"},\n\t\"groq\": {URL: \"https://api.groq.com/openai\"},\n\t\"llama\": {URL: \"https://api.groq.com/openai\"},\n\t\"minimax\": {URL: \"https://api.minimax.io/anthropic\"},\n\t\"hf:\": {URL: \"https://api.synthetic.new/anthropic\"}, // HuggingFace\n}\n\n// BuiltinProviders returns a copy of the builtin provider map.\n// Used by diagnostic tools (e.g., crust doctor) to enumerate all known providers.\nfunc BuiltinProviders() map[string]config.ProviderConfig {\n\tresult := make(map[string]config.ProviderConfig, len(builtinProviders))\n\tmaps.Copy(result, builtinProviders)\n\treturn result\n}\n\n// ResolveProvider resolves a model name to a provider config (URL + optional API key).\n//\n// Matching order:\n// 1. If model starts with \"hf:\", route to HuggingFace provider\n// 2. If model contains \"/\", take the part before \"/\" and do exact match\n// 3. Otherwise, combined prefix + hyphen-segment matching, longest wins\n//\n// User-defined providers are checked first (higher priority), then builtins.\nfunc ResolveProvider(model string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tif model == \"\" {\n\t\treturn config.ProviderConfig{}, false\n\t}\n\n\tif strings.HasPrefix(model, \"hf:\") {\n\t\treturn lookupExact(\"hf:\", userProviders)\n\t}\n\n\tif vendor, _, ok := strings.Cut(model, \"/\"); ok && vendor != \"\" {\n\t\treturn lookupExact(vendor, userProviders)\n\t}\n\n\treturn lookupBestMatch(model, userProviders)\n}\n\n// lookupExact checks user providers then builtins for an exact key match.\nfunc lookupExact(key string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tkey = strings.ToLower(key)\n\tif prov, ok := userProviders[key]; ok {\n\t\treturn prov, true\n\t}\n\tif prov, ok := builtinProviders[key]; ok {\n\t\treturn prov, true\n\t}\n\treturn config.ProviderConfig{}, false\n}\n\n// lookupBestMatch finds the best matching provider using both prefix and\n// hyphen-delimited segment matching. The longest match wins.\n// User-defined providers are checked first; any user match wins over builtins.\nfunc lookupBestMatch(model string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tlower := strings.ToLower(model)\n\tsegments := strings.Split(lower, \"-\")\n\n\tbestIn := func(m map[string]config.ProviderConfig) (config.ProviderConfig, int) {\n\t\tbest := config.ProviderConfig{}\n\t\tbestLen := 0\n\t\tfor key, prov := range m {\n\t\t\tif (strings.HasPrefix(lower, key) || slices.Contains(segments, key)) && len(key) > bestLen {\n\t\t\t\tbest = prov\n\t\t\t\tbestLen = len(key)\n\t\t\t}\n\t\t}\n\t\treturn best, bestLen\n\t}\n\n\tif result, n := bestIn(userProviders); n > 0 {\n\t\treturn result, true\n\t}\n\tif result, n := bestIn(builtinProviders); n > 0 {\n\t\treturn result, true\n\t}\n\treturn config.ProviderConfig{}, false\n}\n"}, "db178792fa3e3ff59623de6a39380a821a3fb2fd": {"t": 1785759255.8949957, "n": 4258, "h": "eb5aeb2725f71f5b", "c": "package httpproxy\n\nimport (\n\t\"maps\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/BakeLens/crust/internal/config\"\n)\n\n// builtinProviders maps model keyword to provider config.\n// Matching logic: slash-split first (e.g. \"openai/gpt-4o\" → \"openai\"),\n// then combined prefix + hyphen-segment matching (longest wins).\nvar builtinProviders = map[string]config.ProviderConfig{\n\t\"claude\": {URL: \"https://api.anthropic.com\"},\n\t\"gpt\": {URL: \"https://api.openai.com\"},\n\t\"o1\": {URL: \"https://api.openai.com\"},\n\t\"o3\": {URL: \"https://api.openai.com\"},\n\t\"o4\": {URL: \"https://api.openai.com\"},\n\t\"openai\": {URL: \"https://api.openai.com\"},\n\t\"codex\": {URL: \"https://chatgpt.com/backend-api/codex\"},\n\t\"openai-codex\": {URL: \"https://chatgpt.com/backend-api/codex\"},\n\t\"deepseek\": {URL: \"https://api.deepseek.com\"},\n\t\"qwen\": {URL: \"https://dashscope.aliyuncs.com/compatible-mode\"},\n\t\"moonshot\": {URL: \"https://api.moonshot.ai\"},\n\t\"kimi\": {URL: \"https://api.moonshot.ai\"},\n\t// Gemini's OpenAI-compatible endpoint is at /v1beta/openai/, not /v1/.\n\t// With the default URL below, clients sending /v1/chat/completions get\n\t// /v1/chat/completions which returns 404. Users must override this in\n\t// config with the full base URL including the path prefix:\n\t// gemini: https://generativelanguage.googleapis.com/v1beta/openai\n\t\"gemini\": {URL: \"https://generativelanguage.googleapis.com/v1beta/openai\"},\n\t\"glm\": {URL: \"https://open.bigmodel.cn/api/paas/v4\"},\n\t\"mistral\": {URL: \"https://api.mistral.ai\"},\n\t\"groq\": {URL: \"https://api.groq.com/openai\"},\n\t\"llama\": {URL: \"https://api.groq.com/openai\"},\n\t\"minimax\": {URL: \"https://api.minimax.io/anthropic\"},\n\t\"hf:\": {URL: \"https://api.synthetic.new/anthropic\"}, // HuggingFace\n}\n\n// BuiltinProviders returns a copy of the builtin provider map.\n// Used by diagnostic tools (e.g., crust doctor) to enumerate all known providers.\nfunc BuiltinProviders() map[string]config.ProviderConfig {\n\tresult := make(map[string]config.ProviderConfig, len(builtinProviders))\n\tmaps.Copy(result, builtinProviders)\n\treturn result\n}\n\n// ResolveProvider resolves a model name to a provider config (URL + optional API key).\n//\n// Matching order:\n// 1. If model starts with \"hf:\", route to HuggingFace provider\n// 2. If model contains \"/\", take the part before \"/\" and do exact match\n// 3. Otherwise, combined prefix + hyphen-segment matching, longest wins\n//\n// User-defined providers are checked first (higher priority), then builtins.\nfunc ResolveProvider(model string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tif model == \"\" {\n\t\treturn config.ProviderConfig{}, false\n\t}\n\n\tif strings.HasPrefix(model, \"hf:\") {\n\t\treturn lookupExact(\"hf:\", userProviders)\n\t}\n\n\tif vendor, _, ok := strings.Cut(model, \"/\"); ok && vendor != \"\" {\n\t\treturn lookupExact(vendor, userProviders)\n\t}\n\n\treturn lookupBestMatch(model, userProviders)\n}\n\n// lookupExact checks user providers then builtins for an exact key match.\nfunc lookupExact(key string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tkey = strings.ToLower(key)\n\tif prov, ok := userProviders[key]; ok {\n\t\treturn prov, true\n\t}\n\tif prov, ok := builtinProviders[key]; ok {\n\t\treturn prov, true\n\t}\n\treturn config.ProviderConfig{}, false\n}\n\n// lookupBestMatch finds the best matching provider using both prefix and\n// hyphen-delimited segment matching. The longest match wins.\n// User-defined providers are checked first; any user match wins over builtins.\nfunc lookupBestMatch(model string, userProviders map[string]config.ProviderConfig) (config.ProviderConfig, bool) {\n\tlower := strings.ToLower(model)\n\tsegments := strings.Split(lower, \"-\")\n\n\tbestIn := func(m map[string]config.ProviderConfig) (config.ProviderConfig, int) {\n\t\tbest := config.ProviderConfig{}\n\t\tbestLen := 0\n\t\tfor key, prov := range m {\n\t\t\tif (strings.HasPrefix(lower, key) || slices.Contains(segments, key)) && len(key) > bestLen {\n\t\t\t\tbest = prov\n\t\t\t\tbestLen = len(key)\n\t\t\t}\n\t\t}\n\t\treturn best, bestLen\n\t}\n\n\tif result, n := bestIn(userProviders); n > 0 {\n\t\treturn result, true\n\t}\n\tif result, n := bestIn(builtinProviders); n > 0 {\n\t\treturn result, true\n\t}\n\treturn config.ProviderConfig{}, false\n}\n"}}, "internal/configscan/scan.go": {"f4a47e2cf2822196275075ab9a2e258b6fa9be8b": {"t": 1785746055.5814757, "n": 9946, "h": "25177ad9b4b80a78", "c": "// Package configscan detects malicious endpoint redirects in project config files.\n// CVE-2026-21852: .env sets ANTHROPIC_BASE_URL to attacker endpoint, leaking API keys.\npackage configscan\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Finding describes a suspicious endpoint redirect in a config file.\ntype Finding struct {\n\tFile string `json:\"file\"`\n\tVariable string `json:\"variable\"`\n\tValue string `json:\"value\"`\n\tRisk string `json:\"risk\"`\n}\n\n// envRedirectRe matches *_BASE_URL=, *_API_BASE=, *_API_URL=, *_ENDPOINT= patterns.\nvar envRedirectRe = regexp.MustCompile(`^([A-Z_]*(?:BASE_URL|API_BASE|API_URL|ENDPOINT))\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeDomains are official API endpoints that are not suspicious.\nvar knownSafeDomains = map[string]bool{\n\t\"api.anthropic.com\": true,\n\t\"api.openai.com\": true,\n\t\"generativelanguage.googleapis.com\": true,\n\t\"api.groq.com\": true,\n\t\"api.mistral.ai\": true,\n\t\"dashscope.aliyuncs.com\": true,\n\t\"api.deepseek.com\": true,\n\t\"api.cohere.com\": true,\n\t\"openrouter.ai\": true,\n\t\"api.together.xyz\": true,\n\t\"api.fireworks.ai\": true,\n\t\"api.perplexity.ai\": true,\n\t\"api.x.ai\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\n// ScanDir scans a directory and its parents (up to root or home) for\n// config files containing suspicious endpoint redirects.\nfunc ScanDir(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFiles(dir)...)\n\tfindings = append(findings, scanClaudeSettings(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigs(dir)...)\n\treturn findings\n}\n\n// ScanDirOnly scans a single directory without walking parents.\nfunc ScanDirOnly(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigsInDir(dir)...)\n\treturn findings\n}\n\n// walkParents calls scanFn on dir and each parent directory up to (but not\n// above) the user's home directory. Falls back to scanning dir only if home\n// cannot be determined.\nfunc walkParents(dir string, scanFn func(string) []Finding) []Finding {\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanFn(dir)\n\t}\n\n\tvar findings []Finding\n\tfor {\n\t\tfindings = append(findings, scanFn(dir)...)\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir || dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\n// scanEnvFiles scans .env files in the given directory and parent dirs.\nfunc scanEnvFiles(dir string) []Finding {\n\treturn walkParents(dir, scanEnvFilesInDir)\n}\n\nfunc scanEnvFilesInDir(dir string) []Finding {\n\tvar findings []Finding\n\tenvFiles := []string{\".env\", \".env.local\", \".env.production\", \".env.development\"}\n\n\tfor _, name := range envFiles {\n\t\tpath := filepath.Join(dir, name)\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tscanner := bufio.NewScanner(f)\n\t\tfor scanner.Scan() {\n\t\t\tline := strings.TrimSpace(scanner.Text())\n\t\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm := envRedirectRe.FindStringSubmatch(line)\n\t\t\tif m == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvarName, value := m[1], m[2]\n\t\t\tif isSuspiciousURL(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: varName,\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"redirects API traffic to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tf.Close()\n\t}\n\treturn findings\n}\n\n// scanClaudeSettings scans .claude/settings*.json for apiUrl overrides.\nfunc scanClaudeSettings(dir string) []Finding {\n\treturn walkParents(dir, scanClaudeSettingsInDir)\n}\n\nfunc scanClaudeSettingsInDir(dir string) []Finding {\n\tvar findings []Finding\n\tsettingsPath := filepath.Join(dir, \".claude\", \"settings.json\")\n\n\tdata, err := os.ReadFile(settingsPath)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar settings map[string]any\n\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\treturn nil\n\t}\n\n\t// Check for apiUrl field\n\tif apiURL, ok := settings[\"apiUrl\"].(string); ok && apiURL != \"\" {\n\t\tif isSuspiciousURL(apiURL) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: settingsPath,\n\t\t\t\tVariable: \"apiUrl\",\n\t\t\t\tValue: apiURL,\n\t\t\t\tRisk: \"Claude Code API URL overridden to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn findings\n}\n\n// extractHost returns the lowercase hostname from a URL, stripping protocol and path.\nfunc extractHost(rawURL string) string {\n\tu := strings.ToLower(rawURL)\n\tu = strings.TrimPrefix(u, \"https://\")\n\tu = strings.TrimPrefix(u, \"http://\")\n\tif i := strings.IndexAny(u, \"/:\"); i >= 0 {\n\t\tu = u[:i]\n\t}\n\treturn u\n}\n\n// isSuspiciousURL returns true if the URL doesn't point to a known safe API domain.\nfunc isSuspiciousURL(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeDomains[extractHost(rawURL)]\n}\n\n// scanPackageManagerConfigs scans for registry redirects in package manager configs.\nfunc scanPackageManagerConfigs(dir string) []Finding {\n\treturn walkParents(dir, scanPackageManagerConfigsInDir)\n}\n\nfunc scanPackageManagerConfigsInDir(dir string) []Finding {\n\tvar findings []Finding\n\n\t// .npmrc — registry=https://evil.com\n\tfindings = append(findings, scanNpmrc(filepath.Join(dir, \".npmrc\"))...)\n\n\t// pyproject.toml — index-url = \"https://evil.com/simple\"\n\tfindings = append(findings, scanPyprojectToml(filepath.Join(dir, \"pyproject.toml\"))...)\n\n\t// .yarnrc.yml — yarnPath or npmRegistryServer pointing to malicious code/registry\n\tfindings = append(findings, scanYarnrc(filepath.Join(dir, \".yarnrc.yml\"))...)\n\n\treturn findings\n}\n\n// npmrcRegistryRe matches registry=<url> in .npmrc files, including scoped registries (@scope:registry=).\nvar npmrcRegistryRe = regexp.MustCompile(`(?i)^\\s*(?:@[a-z0-9_-]+:)?registry\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeRegistries are official package registries that are not suspicious.\nvar knownSafeRegistries = map[string]bool{\n\t\"registry.npmjs.org\": true,\n\t\"registry.yarnpkg.com\": true,\n\t\"pypi.org\": true,\n\t\"upload.pypi.org\": true,\n\t\"files.pythonhosted.org\": true,\n\t\"crates.io\": true,\n\t\"rubygems.org\": true,\n\t\"repo1.maven.org\": true,\n\t\"repo.maven.apache.org\": true,\n\t\"plugins.gradle.org\": true,\n\t\"jcenter.bintray.com\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\nfunc scanNpmrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \";\") || strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := npmrcRegistryRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"registry\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"npm registry redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml [tool.pip] or [tool.uv] sections.\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml. Handles optional inline comments.\nvar pyprojectIndexRe = regexp.MustCompile(`(?i)^\\s*(?:index-url|extra-index-url)\\s*=\\s*[\"'](\\S+?)[\"']`)\n\nfunc scanPyprojectToml(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tm := pyprojectIndexRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"index-url\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"Python package index redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// yarnrcRe matches yarnPath, npmRegistryServer, or plugin entries in .yarnrc.yml.\n// yarnPath: attacker replaces the Yarn binary with a malicious script (CVE-2025-59828).\n// npmRegistryServer: redirects npm installs to attacker-controlled registry.\nvar yarnrcRe = regexp.MustCompile(`(?i)^\\s*(yarnPath|npmRegistryServer)\\s*:\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\nfunc scanYarnrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := yarnrcRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\tkey, value := m[1], m[2]\n\t\tswitch strings.ToLower(key) {\n\t\tcase \"yarnpath\":\n\t\t\t// Any yarnPath is suspicious — it replaces the Yarn binary\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"yarnPath\",\n\t\t\t\tValue: value,\n\t\t\t\tRisk: \"Yarn binary overridden via yarnPath — potential arbitrary code execution\",\n\t\t\t})\n\t\tcase \"npmregistryserver\":\n\t\t\tif isSuspiciousRegistry(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: \"npmRegistryServer\",\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"Yarn registry redirected to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn findings\n}\n\n// isSuspiciousRegistry returns true if the URL doesn't point to a known safe package registry.\nfunc isSuspiciousRegistry(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeRegistries[extractHost(rawURL)]\n}\n\n// FindingCount returns the number of findings.\nfunc FindingCount(findings []Finding) int {\n\treturn len(findings)\n}\n"}, "2a0dede287b949393d9539973a31c6add244f7c1": {"t": 1785759252.4712386, "n": 9946, "h": "25177ad9b4b80a78", "c": "// Package configscan detects malicious endpoint redirects in project config files.\n// CVE-2026-21852: .env sets ANTHROPIC_BASE_URL to attacker endpoint, leaking API keys.\npackage configscan\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Finding describes a suspicious endpoint redirect in a config file.\ntype Finding struct {\n\tFile string `json:\"file\"`\n\tVariable string `json:\"variable\"`\n\tValue string `json:\"value\"`\n\tRisk string `json:\"risk\"`\n}\n\n// envRedirectRe matches *_BASE_URL=, *_API_BASE=, *_API_URL=, *_ENDPOINT= patterns.\nvar envRedirectRe = regexp.MustCompile(`^([A-Z_]*(?:BASE_URL|API_BASE|API_URL|ENDPOINT))\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeDomains are official API endpoints that are not suspicious.\nvar knownSafeDomains = map[string]bool{\n\t\"api.anthropic.com\": true,\n\t\"api.openai.com\": true,\n\t\"generativelanguage.googleapis.com\": true,\n\t\"api.groq.com\": true,\n\t\"api.mistral.ai\": true,\n\t\"dashscope.aliyuncs.com\": true,\n\t\"api.deepseek.com\": true,\n\t\"api.cohere.com\": true,\n\t\"openrouter.ai\": true,\n\t\"api.together.xyz\": true,\n\t\"api.fireworks.ai\": true,\n\t\"api.perplexity.ai\": true,\n\t\"api.x.ai\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\n// ScanDir scans a directory and its parents (up to root or home) for\n// config files containing suspicious endpoint redirects.\nfunc ScanDir(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFiles(dir)...)\n\tfindings = append(findings, scanClaudeSettings(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigs(dir)...)\n\treturn findings\n}\n\n// ScanDirOnly scans a single directory without walking parents.\nfunc ScanDirOnly(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigsInDir(dir)...)\n\treturn findings\n}\n\n// walkParents calls scanFn on dir and each parent directory up to (but not\n// above) the user's home directory. Falls back to scanning dir only if home\n// cannot be determined.\nfunc walkParents(dir string, scanFn func(string) []Finding) []Finding {\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanFn(dir)\n\t}\n\n\tvar findings []Finding\n\tfor {\n\t\tfindings = append(findings, scanFn(dir)...)\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir || dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\n// scanEnvFiles scans .env files in the given directory and parent dirs.\nfunc scanEnvFiles(dir string) []Finding {\n\treturn walkParents(dir, scanEnvFilesInDir)\n}\n\nfunc scanEnvFilesInDir(dir string) []Finding {\n\tvar findings []Finding\n\tenvFiles := []string{\".env\", \".env.local\", \".env.production\", \".env.development\"}\n\n\tfor _, name := range envFiles {\n\t\tpath := filepath.Join(dir, name)\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tscanner := bufio.NewScanner(f)\n\t\tfor scanner.Scan() {\n\t\t\tline := strings.TrimSpace(scanner.Text())\n\t\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm := envRedirectRe.FindStringSubmatch(line)\n\t\t\tif m == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvarName, value := m[1], m[2]\n\t\t\tif isSuspiciousURL(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: varName,\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"redirects API traffic to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tf.Close()\n\t}\n\treturn findings\n}\n\n// scanClaudeSettings scans .claude/settings*.json for apiUrl overrides.\nfunc scanClaudeSettings(dir string) []Finding {\n\treturn walkParents(dir, scanClaudeSettingsInDir)\n}\n\nfunc scanClaudeSettingsInDir(dir string) []Finding {\n\tvar findings []Finding\n\tsettingsPath := filepath.Join(dir, \".claude\", \"settings.json\")\n\n\tdata, err := os.ReadFile(settingsPath)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar settings map[string]any\n\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\treturn nil\n\t}\n\n\t// Check for apiUrl field\n\tif apiURL, ok := settings[\"apiUrl\"].(string); ok && apiURL != \"\" {\n\t\tif isSuspiciousURL(apiURL) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: settingsPath,\n\t\t\t\tVariable: \"apiUrl\",\n\t\t\t\tValue: apiURL,\n\t\t\t\tRisk: \"Claude Code API URL overridden to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn findings\n}\n\n// extractHost returns the lowercase hostname from a URL, stripping protocol and path.\nfunc extractHost(rawURL string) string {\n\tu := strings.ToLower(rawURL)\n\tu = strings.TrimPrefix(u, \"https://\")\n\tu = strings.TrimPrefix(u, \"http://\")\n\tif i := strings.IndexAny(u, \"/:\"); i >= 0 {\n\t\tu = u[:i]\n\t}\n\treturn u\n}\n\n// isSuspiciousURL returns true if the URL doesn't point to a known safe API domain.\nfunc isSuspiciousURL(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeDomains[extractHost(rawURL)]\n}\n\n// scanPackageManagerConfigs scans for registry redirects in package manager configs.\nfunc scanPackageManagerConfigs(dir string) []Finding {\n\treturn walkParents(dir, scanPackageManagerConfigsInDir)\n}\n\nfunc scanPackageManagerConfigsInDir(dir string) []Finding {\n\tvar findings []Finding\n\n\t// .npmrc — registry=https://evil.com\n\tfindings = append(findings, scanNpmrc(filepath.Join(dir, \".npmrc\"))...)\n\n\t// pyproject.toml — index-url = \"https://evil.com/simple\"\n\tfindings = append(findings, scanPyprojectToml(filepath.Join(dir, \"pyproject.toml\"))...)\n\n\t// .yarnrc.yml — yarnPath or npmRegistryServer pointing to malicious code/registry\n\tfindings = append(findings, scanYarnrc(filepath.Join(dir, \".yarnrc.yml\"))...)\n\n\treturn findings\n}\n\n// npmrcRegistryRe matches registry=<url> in .npmrc files, including scoped registries (@scope:registry=).\nvar npmrcRegistryRe = regexp.MustCompile(`(?i)^\\s*(?:@[a-z0-9_-]+:)?registry\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeRegistries are official package registries that are not suspicious.\nvar knownSafeRegistries = map[string]bool{\n\t\"registry.npmjs.org\": true,\n\t\"registry.yarnpkg.com\": true,\n\t\"pypi.org\": true,\n\t\"upload.pypi.org\": true,\n\t\"files.pythonhosted.org\": true,\n\t\"crates.io\": true,\n\t\"rubygems.org\": true,\n\t\"repo1.maven.org\": true,\n\t\"repo.maven.apache.org\": true,\n\t\"plugins.gradle.org\": true,\n\t\"jcenter.bintray.com\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\nfunc scanNpmrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \";\") || strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := npmrcRegistryRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"registry\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"npm registry redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml [tool.pip] or [tool.uv] sections.\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml. Handles optional inline comments.\nvar pyprojectIndexRe = regexp.MustCompile(`(?i)^\\s*(?:index-url|extra-index-url)\\s*=\\s*[\"'](\\S+?)[\"']`)\n\nfunc scanPyprojectToml(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tm := pyprojectIndexRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"index-url\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"Python package index redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// yarnrcRe matches yarnPath, npmRegistryServer, or plugin entries in .yarnrc.yml.\n// yarnPath: attacker replaces the Yarn binary with a malicious script (CVE-2025-59828).\n// npmRegistryServer: redirects npm installs to attacker-controlled registry.\nvar yarnrcRe = regexp.MustCompile(`(?i)^\\s*(yarnPath|npmRegistryServer)\\s*:\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\nfunc scanYarnrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := yarnrcRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\tkey, value := m[1], m[2]\n\t\tswitch strings.ToLower(key) {\n\t\tcase \"yarnpath\":\n\t\t\t// Any yarnPath is suspicious — it replaces the Yarn binary\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"yarnPath\",\n\t\t\t\tValue: value,\n\t\t\t\tRisk: \"Yarn binary overridden via yarnPath — potential arbitrary code execution\",\n\t\t\t})\n\t\tcase \"npmregistryserver\":\n\t\t\tif isSuspiciousRegistry(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: \"npmRegistryServer\",\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"Yarn registry redirected to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn findings\n}\n\n// isSuspiciousRegistry returns true if the URL doesn't point to a known safe package registry.\nfunc isSuspiciousRegistry(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeRegistries[extractHost(rawURL)]\n}\n\n// FindingCount returns the number of findings.\nfunc FindingCount(findings []Finding) int {\n\treturn len(findings)\n}\n"}, "1df31be53e9658313e62d584cea729acdbbf67e9": {"t": 1785759254.245315, "n": 8431, "h": "012c446464d39adf", "c": "// Package configscan detects malicious endpoint redirects in project config files.\n// CVE-2026-21852: .env sets ANTHROPIC_BASE_URL to attacker endpoint, leaking API keys.\npackage configscan\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Finding describes a suspicious endpoint redirect in a config file.\ntype Finding struct {\n\tFile string `json:\"file\"`\n\tVariable string `json:\"variable\"`\n\tValue string `json:\"value\"`\n\tRisk string `json:\"risk\"`\n}\n\n// envRedirectRe matches *_BASE_URL=, *_API_BASE=, *_API_URL=, *_ENDPOINT= patterns.\nvar envRedirectRe = regexp.MustCompile(`^([A-Z_]*(?:BASE_URL|API_BASE|API_URL|ENDPOINT))\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeDomains are official API endpoints that are not suspicious.\nvar knownSafeDomains = map[string]bool{\n\t\"api.anthropic.com\": true,\n\t\"api.openai.com\": true,\n\t\"generativelanguage.googleapis.com\": true,\n\t\"api.groq.com\": true,\n\t\"api.mistral.ai\": true,\n\t\"dashscope.aliyuncs.com\": true,\n\t\"api.deepseek.com\": true,\n\t\"api.cohere.com\": true,\n\t\"openrouter.ai\": true,\n\t\"api.together.xyz\": true,\n\t\"api.fireworks.ai\": true,\n\t\"api.perplexity.ai\": true,\n\t\"api.x.ai\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\n// ScanDir scans a directory and its parents (up to root or home) for\n// config files containing suspicious endpoint redirects.\nfunc ScanDir(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFiles(dir)...)\n\tfindings = append(findings, scanClaudeSettings(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigs(dir)...)\n\treturn findings\n}\n\n// ScanDirOnly scans a single directory without walking parents.\nfunc ScanDirOnly(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigsInDir(dir)...)\n\treturn findings\n}\n\n// walkParents calls scanFn on dir and each parent directory up to (but not\n// above) the user's home directory. Falls back to scanning dir only if home\n// cannot be determined.\nfunc walkParents(dir string, scanFn func(string) []Finding) []Finding {\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanFn(dir)\n\t}\n\n\tvar findings []Finding\n\tfor {\n\t\tfindings = append(findings, scanFn(dir)...)\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir || dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\n// scanEnvFiles scans .env files in the given directory and parent dirs.\nfunc scanEnvFiles(dir string) []Finding {\n\treturn walkParents(dir, scanEnvFilesInDir)\n}\n\nfunc scanEnvFilesInDir(dir string) []Finding {\n\tvar findings []Finding\n\tenvFiles := []string{\".env\", \".env.local\", \".env.production\", \".env.development\"}\n\n\tfor _, name := range envFiles {\n\t\tpath := filepath.Join(dir, name)\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tscanner := bufio.NewScanner(f)\n\t\tfor scanner.Scan() {\n\t\t\tline := strings.TrimSpace(scanner.Text())\n\t\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm := envRedirectRe.FindStringSubmatch(line)\n\t\t\tif m == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvarName, value := m[1], m[2]\n\t\t\tif isSuspiciousURL(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: varName,\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"redirects API traffic to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tf.Close()\n\t}\n\treturn findings\n}\n\n// scanClaudeSettings scans .claude/settings*.json for apiUrl overrides.\nfunc scanClaudeSettings(dir string) []Finding {\n\treturn walkParents(dir, scanClaudeSettingsInDir)\n}\n\nfunc scanClaudeSettingsInDir(dir string) []Finding {\n\tvar findings []Finding\n\tsettingsPath := filepath.Join(dir, \".claude\", \"settings.json\")\n\n\tdata, err := os.ReadFile(settingsPath)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar settings map[string]any\n\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\treturn nil\n\t}\n\n\t// Check for apiUrl field\n\tif apiURL, ok := settings[\"apiUrl\"].(string); ok && apiURL != \"\" {\n\t\tif isSuspiciousURL(apiURL) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: settingsPath,\n\t\t\t\tVariable: \"apiUrl\",\n\t\t\t\tValue: apiURL,\n\t\t\t\tRisk: \"Claude Code API URL overridden to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn findings\n}\n\n// extractHost returns the lowercase hostname from a URL, stripping protocol and path.\nfunc extractHost(rawURL string) string {\n\tu := strings.ToLower(rawURL)\n\tu = strings.TrimPrefix(u, \"https://\")\n\tu = strings.TrimPrefix(u, \"http://\")\n\tif i := strings.IndexAny(u, \"/:\"); i >= 0 {\n\t\tu = u[:i]\n\t}\n\treturn u\n}\n\n// isSuspiciousURL returns true if the URL doesn't point to a known safe API domain.\nfunc isSuspiciousURL(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeDomains[extractHost(rawURL)]\n}\n\n// scanPackageManagerConfigs scans for registry redirects in package manager configs.\nfunc scanPackageManagerConfigs(dir string) []Finding {\n\treturn walkParents(dir, scanPackageManagerConfigsInDir)\n}\n\nfunc scanPackageManagerConfigsInDir(dir string) []Finding {\n\tvar findings []Finding\n\n\t// .npmrc — registry=https://evil.com\n\tfindings = append(findings, scanNpmrc(filepath.Join(dir, \".npmrc\"))...)\n\n\t// pyproject.toml — index-url = \"https://evil.com/simple\"\n\tfindings = append(findings, scanPyprojectToml(filepath.Join(dir, \"pyproject.toml\"))...)\n\n\treturn findings\n}\n\n// npmrcRegistryRe matches registry=<url> in .npmrc files, including scoped registries (@scope:registry=).\nvar npmrcRegistryRe = regexp.MustCompile(`(?i)^\\s*(?:@[a-z0-9_-]+:)?registry\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeRegistries are official package registries that are not suspicious.\nvar knownSafeRegistries = map[string]bool{\n\t\"registry.npmjs.org\": true,\n\t\"registry.yarnpkg.com\": true,\n\t\"pypi.org\": true,\n\t\"upload.pypi.org\": true,\n\t\"files.pythonhosted.org\": true,\n\t\"crates.io\": true,\n\t\"rubygems.org\": true,\n\t\"repo1.maven.org\": true,\n\t\"repo.maven.apache.org\": true,\n\t\"plugins.gradle.org\": true,\n\t\"jcenter.bintray.com\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\nfunc scanNpmrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \";\") || strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := npmrcRegistryRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"registry\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"npm registry redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml [tool.pip] or [tool.uv] sections.\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml. Handles optional inline comments.\nvar pyprojectIndexRe = regexp.MustCompile(`(?i)^\\s*(?:index-url|extra-index-url)\\s*=\\s*[\"'](\\S+?)[\"']`)\n\nfunc scanPyprojectToml(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tm := pyprojectIndexRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"index-url\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"Python package index redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// isSuspiciousRegistry returns true if the URL doesn't point to a known safe package registry.\nfunc isSuspiciousRegistry(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\treturn !knownSafeRegistries[extractHost(rawURL)]\n}\n\n// FindingCount returns the number of findings.\nfunc FindingCount(findings []Finding) int {\n\treturn len(findings)\n}\n"}, "fffb5c816b7db00710fcb3ee801e3c835f1cebc7": {"t": 1785759255.7768683, "n": 9045, "h": "982cf6680a996050", "c": "// Package configscan detects malicious endpoint redirects in project config files.\n// CVE-2026-21852: .env sets ANTHROPIC_BASE_URL to attacker endpoint, leaking API keys.\npackage configscan\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Finding describes a suspicious endpoint redirect in a config file.\ntype Finding struct {\n\tFile string `json:\"file\"`\n\tVariable string `json:\"variable\"`\n\tValue string `json:\"value\"`\n\tRisk string `json:\"risk\"`\n}\n\n// envRedirectRe matches *_BASE_URL=, *_API_BASE=, *_API_URL=, *_ENDPOINT= patterns.\nvar envRedirectRe = regexp.MustCompile(`^([A-Z_]*(?:BASE_URL|API_BASE|API_URL|ENDPOINT))\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeDomains are official API endpoints that are not suspicious.\nvar knownSafeDomains = map[string]bool{\n\t\"api.anthropic.com\": true,\n\t\"api.openai.com\": true,\n\t\"generativelanguage.googleapis.com\": true,\n\t\"api.groq.com\": true,\n\t\"api.mistral.ai\": true,\n\t\"dashscope.aliyuncs.com\": true,\n\t\"api.deepseek.com\": true,\n\t\"api.cohere.com\": true,\n\t\"openrouter.ai\": true,\n\t\"api.together.xyz\": true,\n\t\"api.fireworks.ai\": true,\n\t\"api.perplexity.ai\": true,\n\t\"api.x.ai\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\n// ScanDir scans a directory and its parents (up to root or home) for\n// config files containing suspicious endpoint redirects.\nfunc ScanDir(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFiles(dir)...)\n\tfindings = append(findings, scanClaudeSettings(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigs(dir)...)\n\treturn findings\n}\n\n// ScanDirOnly scans a single directory without walking parents.\nfunc ScanDirOnly(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\tfindings = append(findings, scanPackageManagerConfigsInDir(dir)...)\n\treturn findings\n}\n\n// scanEnvFiles scans .env files in the given directory and parent dirs.\nfunc scanEnvFiles(dir string) []Finding {\n\tvar findings []Finding\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\t// Can't determine home — only scan the given directory to avoid\n\t\t// walking up to filesystem root.\n\t\treturn scanEnvFilesInDir(dir)\n\t}\n\n\tfor {\n\t\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tbreak // reached root\n\t\t}\n\t\tif dir == home {\n\t\t\tbreak // don't go above home\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\nfunc scanEnvFilesInDir(dir string) []Finding {\n\tvar findings []Finding\n\tenvFiles := []string{\".env\", \".env.local\", \".env.production\", \".env.development\"}\n\n\tfor _, name := range envFiles {\n\t\tpath := filepath.Join(dir, name)\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tscanner := bufio.NewScanner(f)\n\t\tfor scanner.Scan() {\n\t\t\tline := strings.TrimSpace(scanner.Text())\n\t\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm := envRedirectRe.FindStringSubmatch(line)\n\t\t\tif m == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvarName, value := m[1], m[2]\n\t\t\tif isSuspiciousURL(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: varName,\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"redirects API traffic to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tf.Close()\n\t}\n\treturn findings\n}\n\n// scanClaudeSettings scans .claude/settings*.json for apiUrl overrides.\nfunc scanClaudeSettings(dir string) []Finding {\n\tvar findings []Finding\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanClaudeSettingsInDir(dir)\n\t}\n\n\tfor {\n\t\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tbreak\n\t\t}\n\t\tif dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\nfunc scanClaudeSettingsInDir(dir string) []Finding {\n\tvar findings []Finding\n\tsettingsPath := filepath.Join(dir, \".claude\", \"settings.json\")\n\n\tdata, err := os.ReadFile(settingsPath)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar settings map[string]any\n\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\treturn nil\n\t}\n\n\t// Check for apiUrl field\n\tif apiURL, ok := settings[\"apiUrl\"].(string); ok && apiURL != \"\" {\n\t\tif isSuspiciousURL(apiURL) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: settingsPath,\n\t\t\t\tVariable: \"apiUrl\",\n\t\t\t\tValue: apiURL,\n\t\t\t\tRisk: \"Claude Code API URL overridden to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn findings\n}\n\n// isSuspiciousURL returns true if the URL doesn't point to a known safe domain.\nfunc isSuspiciousURL(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\n\t// Case-insensitive protocol stripping\n\tu := strings.ToLower(rawURL)\n\tu = strings.TrimPrefix(u, \"https://\")\n\tu = strings.TrimPrefix(u, \"http://\")\n\n\t// Extract host (before first / or :)\n\thost := u\n\tif i := strings.IndexAny(host, \"/:\"); i >= 0 {\n\t\thost = host[:i]\n\t}\n\n\treturn !knownSafeDomains[host]\n}\n\n// scanPackageManagerConfigs scans for registry redirects in package manager configs\n// in the given directory and parent dirs.\nfunc scanPackageManagerConfigs(dir string) []Finding {\n\tvar findings []Finding\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanPackageManagerConfigsInDir(dir)\n\t}\n\n\tfor {\n\t\tfindings = append(findings, scanPackageManagerConfigsInDir(dir)...)\n\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tbreak\n\t\t}\n\t\tif dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\nfunc scanPackageManagerConfigsInDir(dir string) []Finding {\n\tvar findings []Finding\n\n\t// .npmrc — registry=https://evil.com\n\tfindings = append(findings, scanNpmrc(filepath.Join(dir, \".npmrc\"))...)\n\n\t// pyproject.toml — index-url = \"https://evil.com/simple\"\n\tfindings = append(findings, scanPyprojectToml(filepath.Join(dir, \"pyproject.toml\"))...)\n\n\treturn findings\n}\n\n// npmrcRegistryRe matches registry=<url> in .npmrc files, including scoped registries (@scope:registry=).\nvar npmrcRegistryRe = regexp.MustCompile(`(?i)^\\s*(?:@[a-z0-9_-]+:)?registry\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeRegistries are official package registries that are not suspicious.\nvar knownSafeRegistries = map[string]bool{\n\t\"registry.npmjs.org\": true,\n\t\"registry.yarnpkg.com\": true,\n\t\"pypi.org\": true,\n\t\"upload.pypi.org\": true,\n\t\"files.pythonhosted.org\": true,\n\t\"crates.io\": true,\n\t\"rubygems.org\": true,\n\t\"repo1.maven.org\": true,\n\t\"repo.maven.apache.org\": true,\n\t\"plugins.gradle.org\": true,\n\t\"jcenter.bintray.com\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\nfunc scanNpmrc(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif strings.HasPrefix(line, \";\") || strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tm := npmrcRegistryRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"registry\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"npm registry redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml [tool.pip] or [tool.uv] sections.\n// pyprojectIndexRe matches index-url or extra-index-url in pyproject.toml. Handles optional inline comments.\nvar pyprojectIndexRe = regexp.MustCompile(`(?i)^\\s*(?:index-url|extra-index-url)\\s*=\\s*[\"'](\\S+?)[\"']`)\n\nfunc scanPyprojectToml(path string) []Finding {\n\tvar findings []Finding\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tm := pyprojectIndexRe.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\turl := m[1]\n\t\tif isSuspiciousRegistry(url) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: path,\n\t\t\t\tVariable: \"index-url\",\n\t\t\t\tValue: url,\n\t\t\t\tRisk: \"Python package index redirected to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\treturn findings\n}\n\n// isSuspiciousRegistry returns true if the URL doesn't point to a known safe registry.\nfunc isSuspiciousRegistry(rawURL string) bool {\n\tif rawURL == \"\" {\n\t\treturn false\n\t}\n\n\t// Case-insensitive protocol stripping\n\tu := strings.ToLower(rawURL)\n\tu = strings.TrimPrefix(u, \"https://\")\n\tu = strings.TrimPrefix(u, \"http://\")\n\n\thost := u\n\tif i := strings.IndexAny(host, \"/:\"); i >= 0 {\n\t\thost = host[:i]\n\t}\n\treturn !knownSafeRegistries[host]\n}\n\n// FindingCount returns the number of findings.\nfunc FindingCount(findings []Finding) int {\n\treturn len(findings)\n}\n"}, "ef104c08bed53a221f5eccb2ade5e1dabfe68a3f": {"t": 1785759259.3849578, "n": 5206, "h": "f0a5b11b9ba1d787", "c": "// Package configscan detects malicious endpoint redirects in project config files.\n// CVE-2026-21852: .env sets ANTHROPIC_BASE_URL to attacker endpoint, leaking API keys.\npackage configscan\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Finding describes a suspicious endpoint redirect in a config file.\ntype Finding struct {\n\tFile string `json:\"file\"`\n\tVariable string `json:\"variable\"`\n\tValue string `json:\"value\"`\n\tRisk string `json:\"risk\"`\n}\n\n// envRedirectRe matches *_BASE_URL=, *_API_BASE=, *_API_URL=, *_ENDPOINT= patterns.\nvar envRedirectRe = regexp.MustCompile(`^([A-Z_]*(?:BASE_URL|API_BASE|API_URL|ENDPOINT))\\s*=\\s*[\"']?(\\S+?)[\"']?\\s*$`)\n\n// knownSafeDomains are official API endpoints that are not suspicious.\nvar knownSafeDomains = map[string]bool{\n\t\"api.anthropic.com\": true,\n\t\"api.openai.com\": true,\n\t\"generativelanguage.googleapis.com\": true,\n\t\"api.groq.com\": true,\n\t\"api.mistral.ai\": true,\n\t\"dashscope.aliyuncs.com\": true,\n\t\"api.deepseek.com\": true,\n\t\"api.cohere.com\": true,\n\t\"openrouter.ai\": true,\n\t\"api.together.xyz\": true,\n\t\"api.fireworks.ai\": true,\n\t\"api.perplexity.ai\": true,\n\t\"api.x.ai\": true,\n\t\"localhost\": true,\n\t\"127.0.0.1\": true,\n\t\"0.0.0.0\": true,\n\t\"::1\": true,\n}\n\n// ScanDir scans a directory and its parents (up to root or home) for\n// config files containing suspicious endpoint redirects.\nfunc ScanDir(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFiles(dir)...)\n\tfindings = append(findings, scanClaudeSettings(dir)...)\n\treturn findings\n}\n\n// ScanDirOnly scans a single directory without walking parents.\nfunc ScanDirOnly(dir string) []Finding {\n\tvar findings []Finding\n\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\treturn findings\n}\n\n// scanEnvFiles scans .env files in the given directory and parent dirs.\nfunc scanEnvFiles(dir string) []Finding {\n\tvar findings []Finding\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\t// Can't determine home — only scan the given directory to avoid\n\t\t// walking up to filesystem root.\n\t\treturn scanEnvFilesInDir(dir)\n\t}\n\n\tfor {\n\t\tfindings = append(findings, scanEnvFilesInDir(dir)...)\n\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tbreak // reached root\n\t\t}\n\t\tif dir == home {\n\t\t\tbreak // don't go above home\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\nfunc scanEnvFilesInDir(dir string) []Finding {\n\tvar findings []Finding\n\tenvFiles := []string{\".env\", \".env.local\", \".env.production\", \".env.development\"}\n\n\tfor _, name := range envFiles {\n\t\tpath := filepath.Join(dir, name)\n\t\tf, err := os.Open(path)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tscanner := bufio.NewScanner(f)\n\t\tfor scanner.Scan() {\n\t\t\tline := strings.TrimSpace(scanner.Text())\n\t\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm := envRedirectRe.FindStringSubmatch(line)\n\t\t\tif m == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvarName, value := m[1], m[2]\n\t\t\tif isSuspiciousURL(value) {\n\t\t\t\tfindings = append(findings, Finding{\n\t\t\t\t\tFile: path,\n\t\t\t\t\tVariable: varName,\n\t\t\t\t\tValue: value,\n\t\t\t\t\tRisk: \"redirects API traffic to non-official endpoint\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tf.Close()\n\t}\n\treturn findings\n}\n\n// scanClaudeSettings scans .claude/settings*.json for apiUrl overrides.\nfunc scanClaudeSettings(dir string) []Finding {\n\tvar findings []Finding\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn scanClaudeSettingsInDir(dir)\n\t}\n\n\tfor {\n\t\tfindings = append(findings, scanClaudeSettingsInDir(dir)...)\n\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir {\n\t\t\tbreak\n\t\t}\n\t\tif dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn findings\n}\n\nfunc scanClaudeSettingsInDir(dir string) []Finding {\n\tvar findings []Finding\n\tsettingsPath := filepath.Join(dir, \".claude\", \"settings.json\")\n\n\tdata, err := os.ReadFile(settingsPath)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar settings map[string]any\n\tif err := json.Unmarshal(data, &settings); err != nil {\n\t\treturn nil\n\t}\n\n\t// Check for apiUrl field\n\tif apiURL, ok := settings[\"apiUrl\"].(string); ok && apiURL != \"\" {\n\t\tif isSuspiciousURL(apiURL) {\n\t\t\tfindings = append(findings, Finding{\n\t\t\t\tFile: settingsPath,\n\t\t\t\tVariable: \"apiUrl\",\n\t\t\t\tValue: apiURL,\n\t\t\t\tRisk: \"Claude Code API URL overridden to non-official endpoint\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn findings\n}\n\n// isSuspiciousURL returns true if the URL doesn't point to a known safe domain.\nfunc isSuspiciousURL(rawURL string) bool {\n\t// Strip protocol\n\tu := rawURL\n\tfor _, prefix := range []string{\"https://\", \"http://\", \"HTTP://\", \"HTTPS://\"} {\n\t\tu = strings.TrimPrefix(u, prefix)\n\t}\n\n\t// Extract host (before first / or :)\n\thost := u\n\tif i := strings.IndexAny(host, \"/:\"); i >= 0 {\n\t\thost = host[:i]\n\t}\n\thost = strings.ToLower(host)\n\n\treturn !knownSafeDomains[host]\n}\n\n// FindingCount returns the number of findings.\nfunc FindingCount(findings []Finding) int {\n\treturn len(findings)\n}\n"}}, "internal/httpproxy/providers_test.go": {"f4a47e2cf2822196275075ab9a2e258b6fa9be8b": {"t": 1785746083.2287037, "n": 5707, "h": "dfae0eb76615d79a", "c": "package httpproxy\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/BakeLens/crust/internal/config\"\n\t\"github.com/BakeLens/crust/internal/types\"\n)\n\nfunc TestResolveProvider_SlashSplit(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t\twantOK bool\n\t}{\n\t\t{\"openai/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"claude/claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\", true},\n\t\t{\"gpt/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"unknown/model\", \"\", false},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif ok != tt.wantOK {\n\t\t\tt.Errorf(\"ResolveProvider(%q) ok=%v, want %v\", tt.model, ok, tt.wantOK)\n\t\t\tcontinue\n\t\t}\n\t\tif ok && result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_BuiltinMatch(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t}{\n\t\t// Prefix matches\n\t\t{\"deepseek-chat\", \"https://api.deepseek.com\"},\n\t\t{\"deepseek-coder-v2\", \"https://api.deepseek.com\"},\n\t\t{\"claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\"},\n\t\t{\"claude-3-opus-20240229\", \"https://api.anthropic.com\"},\n\t\t{\"gpt-4o\", \"https://api.openai.com\"},\n\t\t{\"gpt-4-turbo\", \"https://api.openai.com\"},\n\t\t{\"o1-preview\", \"https://api.openai.com\"},\n\t\t{\"o3-mini\", \"https://api.openai.com\"},\n\t\t{\"o4-mini\", \"https://api.openai.com\"},\n\t\t{\"gemini-pro\", \"https://generativelanguage.googleapis.com/v1beta/openai\"},\n\t\t{\"llama-3.3-70b-versatile\", \"https://api.groq.com/openai\"},\n\t\t{\"mistral-large\", \"https://api.mistral.ai\"},\n\t\t{\"moonshot-v1-8k\", \"https://api.moonshot.ai\"},\n\t\t{\"kimi-latest\", \"https://api.moonshot.ai\"},\n\t\t{\"qwen-turbo\", \"https://dashscope.aliyuncs.com/compatible-mode\"},\n\t\t{\"minimax-abab5.5\", \"https://api.minimax.io/anthropic\"},\n\t\t{\"groq-llama-3\", \"https://api.groq.com/openai\"},\n\t\t// Codex segment matches (segment \"codex\" len 5 beats prefix \"gpt\" len 3)\n\t\t{\"gpt-5.3-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.2-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-mini\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-max\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"codex-mini-latest\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t// HuggingFace\n\t\t{\"hf:Meta-Llama-3.1-8B-Instruct\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:Qwen2.5-Coder-32B\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:moonshotai/Kimi-K2-Thinking\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:deepseek-ai/DeepSeek-R1-0528\", \"https://api.synthetic.new/anthropic\"},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif !ok {\n\t\t\tt.Errorf(\"expected match for %q\", tt.model)\n\t\t\tcontinue\n\t\t}\n\t\tif result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_NoMatch(t *testing.T) {\n\tfor _, model := range []string{\"\", \"unknown-model\"} {\n\t\tif _, ok := ResolveProvider(model, nil); ok {\n\t\t\tt.Errorf(\"expected no match for %q\", model)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_UserProviders(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tmodel string\n\t\tproviders map[string]config.ProviderConfig\n\t\twantURL string\n\t\twantKey string\n\t}{\n\t\t{\n\t\t\tname: \"user priority over builtin\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"http://localhost:8000\"}},\n\t\t\twantURL: \"http://localhost:8000\",\n\t\t},\n\t\t{\n\t\t\tname: \"custom model prefix\",\n\t\t\tmodel: \"my-llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"my-llama\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"slash split with user provider\",\n\t\t\tmodel: \"local/llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"local\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"longest prefix wins\",\n\t\t\tmodel: \"o3-mini\",\n\t\t\tproviders: map[string]config.ProviderConfig{\n\t\t\t\t\"o\": {URL: \"http://short.example.com\"},\n\t\t\t\t\"o3\": {URL: \"http://o3.example.com\"},\n\t\t\t},\n\t\t\twantURL: \"http://o3.example.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"per-provider API key\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"https://api.deepseek.com\", APIKey: \"sk-deepseek-test\"}},\n\t\t\twantURL: \"https://api.deepseek.com\",\n\t\t\twantKey: \"sk-deepseek-test\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult, ok := ResolveProvider(tt.model, tt.providers)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"expected match for %q\", tt.model)\n\t\t\t}\n\t\t\tif result.URL != tt.wantURL {\n\t\t\t\tt.Errorf(\"URL = %q, want %q\", result.URL, tt.wantURL)\n\t\t\t}\n\t\t\tif result.APIKey != tt.wantKey {\n\t\t\t\tt.Errorf(\"APIKey = %q, want %q\", result.APIKey, tt.wantKey)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResolveProvider_BuiltinHasNoAPIKey(t *testing.T) {\n\tresult, ok := ResolveProvider(\"gpt-4o\", nil)\n\tif !ok {\n\t\tt.Fatal(\"expected match for gpt-4o\")\n\t}\n\tif result.APIKey != \"\" {\n\t\tt.Fatalf(\"expected empty api key for builtin provider, got %s\", result.APIKey)\n\t}\n}\n\nfunc TestRequestContext_String(t *testing.T) {\n\tctx := &RequestContext{\n\t\tModel: \"gpt-4o\",\n\t\tTargetURL: \"https://api.openai.com/v1/chat/completions\",\n\t\tAPIType: types.APITypeOpenAICompletion,\n\t\tProviderAPIKey: \"sk-secret-key-12345\",\n\t}\n\ts := ctx.String()\n\tif strings.Contains(s, \"sk-secret-key-12345\") {\n\t\tt.Error(\"ProviderAPIKey should not appear in String()\")\n\t}\n\tif !strings.Contains(s, \"gpt-4o\") {\n\t\tt.Error(\"expected Model in String()\")\n\t}\n\tif !strings.Contains(s, \"https://api.openai.com\") {\n\t\tt.Error(\"expected TargetURL in String()\")\n\t}\n}\n"}, "451aacf894be22d09702b4d6fe930dd832f52412": {"t": 1785759255.180809, "n": 5707, "h": "dfae0eb76615d79a", "c": "package httpproxy\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/BakeLens/crust/internal/config\"\n\t\"github.com/BakeLens/crust/internal/types\"\n)\n\nfunc TestResolveProvider_SlashSplit(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t\twantOK bool\n\t}{\n\t\t{\"openai/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"claude/claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\", true},\n\t\t{\"gpt/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"unknown/model\", \"\", false},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif ok != tt.wantOK {\n\t\t\tt.Errorf(\"ResolveProvider(%q) ok=%v, want %v\", tt.model, ok, tt.wantOK)\n\t\t\tcontinue\n\t\t}\n\t\tif ok && result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_BuiltinMatch(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t}{\n\t\t// Prefix matches\n\t\t{\"deepseek-chat\", \"https://api.deepseek.com\"},\n\t\t{\"deepseek-coder-v2\", \"https://api.deepseek.com\"},\n\t\t{\"claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\"},\n\t\t{\"claude-3-opus-20240229\", \"https://api.anthropic.com\"},\n\t\t{\"gpt-4o\", \"https://api.openai.com\"},\n\t\t{\"gpt-4-turbo\", \"https://api.openai.com\"},\n\t\t{\"o1-preview\", \"https://api.openai.com\"},\n\t\t{\"o3-mini\", \"https://api.openai.com\"},\n\t\t{\"o4-mini\", \"https://api.openai.com\"},\n\t\t{\"gemini-pro\", \"https://generativelanguage.googleapis.com/v1beta/openai\"},\n\t\t{\"llama-3.3-70b-versatile\", \"https://api.groq.com/openai\"},\n\t\t{\"mistral-large\", \"https://api.mistral.ai\"},\n\t\t{\"moonshot-v1-8k\", \"https://api.moonshot.ai\"},\n\t\t{\"kimi-latest\", \"https://api.moonshot.ai\"},\n\t\t{\"qwen-turbo\", \"https://dashscope.aliyuncs.com/compatible-mode\"},\n\t\t{\"minimax-abab5.5\", \"https://api.minimax.io/anthropic\"},\n\t\t{\"groq-llama-3\", \"https://api.groq.com/openai\"},\n\t\t// Codex segment matches (segment \"codex\" len 5 beats prefix \"gpt\" len 3)\n\t\t{\"gpt-5.3-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.2-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-mini\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-max\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"codex-mini-latest\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t// HuggingFace\n\t\t{\"hf:Meta-Llama-3.1-8B-Instruct\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:Qwen2.5-Coder-32B\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:moonshotai/Kimi-K2-Thinking\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:deepseek-ai/DeepSeek-R1-0528\", \"https://api.synthetic.new/anthropic\"},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif !ok {\n\t\t\tt.Errorf(\"expected match for %q\", tt.model)\n\t\t\tcontinue\n\t\t}\n\t\tif result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_NoMatch(t *testing.T) {\n\tfor _, model := range []string{\"\", \"unknown-model\"} {\n\t\tif _, ok := ResolveProvider(model, nil); ok {\n\t\t\tt.Errorf(\"expected no match for %q\", model)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_UserProviders(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tmodel string\n\t\tproviders map[string]config.ProviderConfig\n\t\twantURL string\n\t\twantKey string\n\t}{\n\t\t{\n\t\t\tname: \"user priority over builtin\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"http://localhost:8000\"}},\n\t\t\twantURL: \"http://localhost:8000\",\n\t\t},\n\t\t{\n\t\t\tname: \"custom model prefix\",\n\t\t\tmodel: \"my-llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"my-llama\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"slash split with user provider\",\n\t\t\tmodel: \"local/llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"local\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"longest prefix wins\",\n\t\t\tmodel: \"o3-mini\",\n\t\t\tproviders: map[string]config.ProviderConfig{\n\t\t\t\t\"o\": {URL: \"http://short.example.com\"},\n\t\t\t\t\"o3\": {URL: \"http://o3.example.com\"},\n\t\t\t},\n\t\t\twantURL: \"http://o3.example.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"per-provider API key\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"https://api.deepseek.com\", APIKey: \"sk-deepseek-test\"}},\n\t\t\twantURL: \"https://api.deepseek.com\",\n\t\t\twantKey: \"sk-deepseek-test\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult, ok := ResolveProvider(tt.model, tt.providers)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"expected match for %q\", tt.model)\n\t\t\t}\n\t\t\tif result.URL != tt.wantURL {\n\t\t\t\tt.Errorf(\"URL = %q, want %q\", result.URL, tt.wantURL)\n\t\t\t}\n\t\t\tif result.APIKey != tt.wantKey {\n\t\t\t\tt.Errorf(\"APIKey = %q, want %q\", result.APIKey, tt.wantKey)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResolveProvider_BuiltinHasNoAPIKey(t *testing.T) {\n\tresult, ok := ResolveProvider(\"gpt-4o\", nil)\n\tif !ok {\n\t\tt.Fatal(\"expected match for gpt-4o\")\n\t}\n\tif result.APIKey != \"\" {\n\t\tt.Fatalf(\"expected empty api key for builtin provider, got %s\", result.APIKey)\n\t}\n}\n\nfunc TestRequestContext_String(t *testing.T) {\n\tctx := &RequestContext{\n\t\tModel: \"gpt-4o\",\n\t\tTargetURL: \"https://api.openai.com/v1/chat/completions\",\n\t\tAPIType: types.APITypeOpenAICompletion,\n\t\tProviderAPIKey: \"sk-secret-key-12345\",\n\t}\n\ts := ctx.String()\n\tif strings.Contains(s, \"sk-secret-key-12345\") {\n\t\tt.Error(\"ProviderAPIKey should not appear in String()\")\n\t}\n\tif !strings.Contains(s, \"gpt-4o\") {\n\t\tt.Error(\"expected Model in String()\")\n\t}\n\tif !strings.Contains(s, \"https://api.openai.com\") {\n\t\tt.Error(\"expected TargetURL in String()\")\n\t}\n}\n"}, "db178792fa3e3ff59623de6a39380a821a3fb2fd": {"t": 1785759256.6911585, "n": 5642, "h": "029b0510635cd418", "c": "package httpproxy\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/BakeLens/crust/internal/config\"\n)\n\nfunc TestResolveProvider_SlashSplit(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t\twantOK bool\n\t}{\n\t\t{\"openai/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"claude/claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\", true},\n\t\t{\"gpt/gpt-4o\", \"https://api.openai.com\", true},\n\t\t{\"unknown/model\", \"\", false},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif ok != tt.wantOK {\n\t\t\tt.Errorf(\"ResolveProvider(%q) ok=%v, want %v\", tt.model, ok, tt.wantOK)\n\t\t\tcontinue\n\t\t}\n\t\tif ok && result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_BuiltinMatch(t *testing.T) {\n\ttests := []struct {\n\t\tmodel string\n\t\twantURL string\n\t}{\n\t\t// Prefix matches\n\t\t{\"deepseek-chat\", \"https://api.deepseek.com\"},\n\t\t{\"deepseek-coder-v2\", \"https://api.deepseek.com\"},\n\t\t{\"claude-sonnet-4-5-20250929\", \"https://api.anthropic.com\"},\n\t\t{\"claude-3-opus-20240229\", \"https://api.anthropic.com\"},\n\t\t{\"gpt-4o\", \"https://api.openai.com\"},\n\t\t{\"gpt-4-turbo\", \"https://api.openai.com\"},\n\t\t{\"o1-preview\", \"https://api.openai.com\"},\n\t\t{\"o3-mini\", \"https://api.openai.com\"},\n\t\t{\"o4-mini\", \"https://api.openai.com\"},\n\t\t{\"gemini-pro\", \"https://generativelanguage.googleapis.com/v1beta/openai\"},\n\t\t{\"llama-3.3-70b-versatile\", \"https://api.groq.com/openai\"},\n\t\t{\"mistral-large\", \"https://api.mistral.ai\"},\n\t\t{\"moonshot-v1-8k\", \"https://api.moonshot.ai\"},\n\t\t{\"kimi-latest\", \"https://api.moonshot.ai\"},\n\t\t{\"qwen-turbo\", \"https://dashscope.aliyuncs.com/compatible-mode\"},\n\t\t{\"minimax-abab5.5\", \"https://api.minimax.io/anthropic\"},\n\t\t{\"groq-llama-3\", \"https://api.groq.com/openai\"},\n\t\t// Codex segment matches (segment \"codex\" len 5 beats prefix \"gpt\" len 3)\n\t\t{\"gpt-5.3-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.2-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-mini\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5.1-codex-max\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"gpt-5-codex\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t{\"codex-mini-latest\", \"https://chatgpt.com/backend-api/codex\"},\n\t\t// HuggingFace\n\t\t{\"hf:Meta-Llama-3.1-8B-Instruct\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:Qwen2.5-Coder-32B\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:moonshotai/Kimi-K2-Thinking\", \"https://api.synthetic.new/anthropic\"},\n\t\t{\"hf:deepseek-ai/DeepSeek-R1-0528\", \"https://api.synthetic.new/anthropic\"},\n\t}\n\tfor _, tt := range tests {\n\t\tresult, ok := ResolveProvider(tt.model, nil)\n\t\tif !ok {\n\t\t\tt.Errorf(\"expected match for %q\", tt.model)\n\t\t\tcontinue\n\t\t}\n\t\tif result.URL != tt.wantURL {\n\t\t\tt.Errorf(\"ResolveProvider(%q) = %q, want %q\", tt.model, result.URL, tt.wantURL)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_NoMatch(t *testing.T) {\n\tfor _, model := range []string{\"\", \"unknown-model\"} {\n\t\tif _, ok := ResolveProvider(model, nil); ok {\n\t\t\tt.Errorf(\"expected no match for %q\", model)\n\t\t}\n\t}\n}\n\nfunc TestResolveProvider_UserProviders(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tmodel string\n\t\tproviders map[string]config.ProviderConfig\n\t\twantURL string\n\t\twantKey string\n\t}{\n\t\t{\n\t\t\tname: \"user priority over builtin\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"http://localhost:8000\"}},\n\t\t\twantURL: \"http://localhost:8000\",\n\t\t},\n\t\t{\n\t\t\tname: \"custom model prefix\",\n\t\t\tmodel: \"my-llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"my-llama\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"slash split with user provider\",\n\t\t\tmodel: \"local/llama-70b\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"local\": {URL: \"http://localhost:11434/v1\"}},\n\t\t\twantURL: \"http://localhost:11434/v1\",\n\t\t},\n\t\t{\n\t\t\tname: \"longest prefix wins\",\n\t\t\tmodel: \"o3-mini\",\n\t\t\tproviders: map[string]config.ProviderConfig{\n\t\t\t\t\"o\": {URL: \"http://short.example.com\"},\n\t\t\t\t\"o3\": {URL: \"http://o3.example.com\"},\n\t\t\t},\n\t\t\twantURL: \"http://o3.example.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"per-provider API key\",\n\t\t\tmodel: \"deepseek-chat\",\n\t\t\tproviders: map[string]config.ProviderConfig{\"deepseek\": {URL: \"https://api.deepseek.com\", APIKey: \"sk-deepseek-test\"}},\n\t\t\twantURL: \"https://api.deepseek.com\",\n\t\t\twantKey: \"sk-deepseek-test\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult, ok := ResolveProvider(tt.model, tt.providers)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"expected match for %q\", tt.model)\n\t\t\t}\n\t\t\tif result.URL != tt.wantURL {\n\t\t\t\tt.Errorf(\"URL = %q, want %q\", result.URL, tt.wantURL)\n\t\t\t}\n\t\t\tif result.APIKey != tt.wantKey {\n\t\t\t\tt.Errorf(\"APIKey = %q, want %q\", result.APIKey, tt.wantKey)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResolveProvider_BuiltinHasNoAPIKey(t *testing.T) {\n\tresult, ok := ResolveProvider(\"gpt-4o\", nil)\n\tif !ok {\n\t\tt.Fatal(\"expected match for gpt-4o\")\n\t}\n\tif result.APIKey != \"\" {\n\t\tt.Fatalf(\"expected empty api key for builtin provider, got %s\", result.APIKey)\n\t}\n}\n\nfunc TestRequestContext_String(t *testing.T) {\n\tctx := &RequestContext{\n\t\tModel: \"gpt-4o\",\n\t\tTargetURL: \"https://api.openai.com/v1/chat/completions\",\n\t\tAPIType: \"openai\",\n\t\tProviderAPIKey: \"sk-secret-key-12345\",\n\t}\n\ts := ctx.String()\n\tif strings.Contains(s, \"sk-secret-key-12345\") {\n\t\tt.Error(\"ProviderAPIKey should not appear in String()\")\n\t}\n\tif !strings.Contains(s, \"gpt-4o\") {\n\t\tt.Error(\"expected Model in String()\")\n\t}\n\tif !strings.Contains(s, \"https://api.openai.com\") {\n\t\tt.Error(\"expected TargetURL in String()\")\n\t}\n}\n"}}}}