diff --git a/common/arg.cpp b/common/arg.cpp index 2c2ecbd725..b9d5fc42a2 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -697,7 +697,7 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context } }; - // parse the first time to get -hf option (used for remote preset) + // parse all CLI args now, so that -hf is available below for remote preset resolution parse_cli_args(); postprocess_cpu_params(params.cpuparams, nullptr); @@ -750,7 +750,7 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context if (!params.server_tools.empty() && !params.cors_origins_explicit) { LOG_WRN("server tools are enabled, using localhost as default CORS origin (change via --cors-origins)\n"); - params.cors_origins = "http://localhost,http://127.0.0.1,http://0.0.0.0,http://[::1]"; + params.cors_origins = "localhost"; } // pad tensor_buft_overrides for llama_params_fit: @@ -3017,7 +3017,10 @@ common_params_context common_params_parser_init(common_params & params, llama_ex ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_STATIC_PATH")); add_opt(common_arg( {"--cors-origins"}, "ORIGINS", - string_format("comma-separated list of allowed origins for CORS (default: %s)", params.cors_origins.c_str()), + string_format( + "comma-separated list of allowed origins for CORS (default: %s)\n" + "if set to special value 'localhost', reflect the Origin header only if it is localhost", + params.cors_origins.c_str()), [](common_params & params, const std::string & value) { params.cors_origins = value; params.cors_origins_explicit = true; @@ -3100,6 +3103,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex params.server_tools.clear(); params.ui_mcp_proxy = false; } + // note: do not modify cors_origins here, as the options are not evaluated in order (user may explicitly set --cors-origins before --agent) } ).set_examples({LLAMA_EXAMPLE_SERVER}).set_env("LLAMA_ARG_AGENT")); add_opt(common_arg( diff --git a/tools/server/server-http.cpp b/tools/server/server-http.cpp index 95a19af256..e8cc5a50a0 100644 --- a/tools/server/server-http.cpp +++ b/tools/server/server-http.cpp @@ -47,6 +47,16 @@ static void log_server_request(const httplib::Request & req, const httplib::Resp SRV_DBG("response: %s\n", res.body.c_str()); } +// returns true if the Origin header value's host is localhost / 127.0.0.1 / ::1 (any port) +static bool origin_is_localhost(const std::string & origin) { + try { + const std::string host = common_http_parse_url(origin).host; + return host == "localhost" || host == "127.0.0.1" || host == "::1"; + } catch (const std::exception &) { + return false; + } +} + // For Google Cloud Platform deployment compatibility struct gcp_params { bool enabled; @@ -267,9 +277,15 @@ bool server_http_context::init(const common_params & params) { // register server middlewares srv->set_pre_routing_handler([¶ms, middleware_validate_api_key, middleware_server_state](const httplib::Request & req, httplib::Response & res) { - // special case (for convenience): echo back the Origin header to allow any origin to access the server with credentials if (params.cors_credentials && params.cors_origins == "*") { + // special case: echo back the Origin header to allow any origin to access the server with credentials res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin")); + } else if (params.cors_origins == "localhost") { + // special case: only reflect the Origin header if it is a localhost origin + std::string origin = req.get_header_value("Origin"); + if (origin_is_localhost(origin)) { + res.set_header("Access-Control-Allow-Origin", origin); + } } else { res.set_header("Access-Control-Allow-Origin", params.cors_origins); } diff --git a/tools/server/server.cpp b/tools/server/server.cpp index c2b21120af..f307c960db 100644 --- a/tools/server/server.cpp +++ b/tools/server/server.cpp @@ -303,6 +303,14 @@ int llama_server(common_params & params, int argc, char ** argv) { return res; }; + if (params.cors_origins == "*" && params.api_keys.empty()) { + SRV_WRN("%s", "-----------------\n"); + SRV_WRN("%s", "CORS is set to allow all origins ('*') and no API key is set\n"); + SRV_WRN("%s", "this can be a security risk (cross-origin attacks)\n"); + SRV_WRN("%s", "more info: ...\n"); + SRV_WRN("%s", "-----------------\n"); + } + // CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP) if (params.ui_mcp_proxy) { SRV_WRN("%s", "-----------------\n");