mirror of
https://github.com/instructkr/claw-code.git
synced 2026-10-18 00:00:11 +08:00
Merge pull request #3271 from Aneesh-382005/fix/local-model-validation-bypass
fix: generalize local-model validation bypass beyond Ollama tags
This commit is contained in:
6 files changed
+89
-7
No files matched your search
@@ -262,4 +262,21 @@ mod tests {
|
||||
other => panic!("Expected ProviderClient::OpenAi for local model, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_local_openai_compatible_base_url_is_reachable_via_crate_root() {
|
||||
// Regression: this helper must stay `pub` and re-exported from the
|
||||
// crate root so `rusty-claude-cli`'s model-syntax validation can
|
||||
// reuse the same local/private-address detection used here to
|
||||
// waive the API-key requirement.
|
||||
assert!(crate::is_local_openai_compatible_base_url(
|
||||
"http://127.0.0.1:1234/v1"
|
||||
));
|
||||
assert!(crate::is_local_openai_compatible_base_url(
|
||||
"http://192.168.1.50:8000/v1"
|
||||
));
|
||||
assert!(!crate::is_local_openai_compatible_base_url(
|
||||
"https://openrouter.ai/api/v1"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,9 @@ pub use prompt_cache::{
|
||||
pub use providers::anthropic::{AnthropicClient, AnthropicClient as ApiClient, AuthSource};
|
||||
pub use providers::openai_compat::{
|
||||
build_chat_completion_request, check_request_body_size, estimate_request_body_size,
|
||||
flatten_tool_result_content, is_reasoning_model, model_rejects_is_error_field,
|
||||
model_requires_reasoning_content_in_history, translate_message, OpenAiCompatClient,
|
||||
OpenAiCompatConfig,
|
||||
flatten_tool_result_content, is_local_openai_compatible_base_url, is_reasoning_model,
|
||||
model_rejects_is_error_field, model_requires_reasoning_content_in_history, translate_message,
|
||||
OpenAiCompatClient, OpenAiCompatConfig,
|
||||
};
|
||||
pub use providers::{
|
||||
detect_provider_kind, max_tokens_for_model, max_tokens_for_model_with_override,
|
||||
|
||||
@@ -1027,7 +1027,12 @@ fn url_host(url: &str) -> &str {
|
||||
host_port.split(':').next().unwrap_or("")
|
||||
}
|
||||
|
||||
fn is_local_openai_compatible_base_url(url: &str) -> bool {
|
||||
/// Returns true when `url`'s host is loopback or a private-network address
|
||||
/// (`localhost`, `127.0.0.1`, `10.x`, `192.168.x`, `172.16-31.x`). Used to
|
||||
/// waive auth requirements for local OpenAI-compatible servers (Ollama, LM
|
||||
/// Studio, llama.cpp, vLLM) that don't expect a real API key.
|
||||
#[must_use]
|
||||
pub fn is_local_openai_compatible_base_url(url: &str) -> bool {
|
||||
let host = url_host(url.trim());
|
||||
if host.eq_ignore_ascii_case("localhost") || host == "::1" {
|
||||
return true;
|
||||
|
||||
@@ -2987,7 +2987,18 @@ fn is_local_openai_model_syntax(model: &str) -> bool {
|
||||
if let Some(rest) = model.strip_prefix("local/") {
|
||||
return !rest.is_empty() && rest.split('/').all(|segment| !segment.is_empty());
|
||||
}
|
||||
std::env::var_os("OPENAI_BASE_URL").is_some() && (model.contains(':') || model.contains('.'))
|
||||
let Ok(base_url) = std::env::var("OPENAI_BASE_URL") else {
|
||||
return false;
|
||||
};
|
||||
// A local/private OPENAI_BASE_URL (Ollama, LM Studio, llama.cpp, vLLM, ...)
|
||||
// is trusted to accept any non-empty model name the server actually
|
||||
// serves, matching the OLLAMA_HOST bypass above. Remote gateways keep the
|
||||
// stricter dot/colon-tag heuristic so a bare typo doesn't silently route
|
||||
// to a paid hosted API.
|
||||
if api::is_local_openai_compatible_base_url(&base_url) {
|
||||
return !model.is_empty();
|
||||
}
|
||||
model.contains(':') || model.contains('.')
|
||||
}
|
||||
|
||||
fn config_alias_for_current_dir(alias: &str) -> Option<String> {
|
||||
@@ -19947,4 +19958,37 @@ mod alias_resolution_tests {
|
||||
// Empty model still rejected
|
||||
assert!(validate_model_syntax("").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_openai_base_url_bypasses_bare_model_names() {
|
||||
let _guard = ollama_env_lock();
|
||||
let _ollama_env = EnvVarGuard::unset("OLLAMA_HOST");
|
||||
// LM Studio's default port; a bare alias with no dot/colon/slash,
|
||||
// as LM Studio and llama.cpp commonly register models under.
|
||||
let _openai_env = EnvVarGuard::set("OPENAI_BASE_URL", "http://127.0.0.1:1234/v1");
|
||||
assert!(validate_model_syntax("mistral").is_ok());
|
||||
assert!(validate_model_syntax("local-7b").is_ok());
|
||||
// Empty model is still rejected even when the base URL is local.
|
||||
assert!(validate_model_syntax("").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_network_openai_base_url_bypasses_bare_model_names() {
|
||||
let _guard = ollama_env_lock();
|
||||
let _ollama_env = EnvVarGuard::unset("OLLAMA_HOST");
|
||||
let _openai_env = EnvVarGuard::set("OPENAI_BASE_URL", "http://192.168.1.50:8000/v1");
|
||||
assert!(validate_model_syntax("mistral").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_openai_base_url_still_requires_provider_model_or_tag_syntax() {
|
||||
let _guard = ollama_env_lock();
|
||||
let _ollama_env = EnvVarGuard::unset("OLLAMA_HOST");
|
||||
let _openai_env = EnvVarGuard::set("OPENAI_BASE_URL", "https://openrouter.ai/api/v1");
|
||||
// A bare name with no dot/colon/slash against a remote gateway must
|
||||
// keep failing so a typo doesn't silently route to a paid hosted API.
|
||||
assert!(validate_model_syntax("mistral").is_err());
|
||||
// The existing dot/colon-tag heuristic still applies for remote hosts.
|
||||
assert!(validate_model_syntax("qwen2.5-coder:7b").is_ok());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user