Harden plugin runtime: TLS verify, LSP deadlock, path traversal, ABI exception safety (W14)
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled

W14 addresses the five most critical findings from the W13 plugin audits:

- W14.1 network: enable ssl::verify_peer + SSL_set1_host SNI hostname
  verification (fixes TLS bypass, W13.3 CVSS 7.4); add steady_timer DNS
  timeout and bottom-up catch(...) hardening (engineer-zhou)
- W14.2 lsp: fix reader_loop/stop mutex deadlock via stop_nolock/stop_locked
  split (W13.4); wrap 11 vtable/entry functions in try/catch with cv
  notification on reader exit (engineer-sun)
- W14.3 tools: add is_safe_path() rejecting empty/absolute/.. paths before
  file_io calls (fixes path traversal, W13.5 CVSS 7.5); guard g_tools and
  g_session/g_history under mutex; 9 vtable try/catch (security-cao)
- W14.4 host: add fallback plugin search (../plugins/) so binaries run from
  build/tests/ load current DLLs, resolving the W13.6 R2 stale-DLL false
  alarm (architect-lin)
- W14.5 anthropic+deepseek: wrap 12 ABI boundary functions in try/catch with
  log-guard, preventing exceptions from crossing the C ABI (engineer-chen)

Verified: cmake build 0 error 0 warning, ctest 4/4 pass, smoke R2 now
passes naturally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 12:03:50 +08:00
parent 47082376ef
commit 102cd3e141
12 changed files with 1230 additions and 702 deletions

View File

@@ -244,20 +244,28 @@ static int my_configure(const char* provider, const char* base_url,
const char* api_key, const char* model,
int max_tokens, double temperature)
{
if (provider) g_cfg.provider = provider;
if (base_url) g_cfg.base_url = base_url;
if (api_key) g_cfg.api_key = api_key;
if (model) g_cfg.model = model;
g_cfg.max_tokens = max_tokens;
g_cfg.temperature = temperature;
try {
if (provider) g_cfg.provider = provider;
if (base_url) g_cfg.base_url = base_url;
if (api_key) g_cfg.api_key = api_key;
if (model) g_cfg.model = model;
g_cfg.max_tokens = max_tokens;
g_cfg.temperature = temperature;
if (g_host) {
g_host->log(DSTALK_LOG_INFO,
"[anthropic] configured: model=%s base_url=%s max_tokens=%d temperature=%.2f",
g_cfg.model.c_str(), g_cfg.base_url.c_str(),
g_cfg.max_tokens, g_cfg.temperature);
if (g_host) {
g_host->log(DSTALK_LOG_INFO,
"[anthropic] configured: model=%s base_url=%s max_tokens=%d temperature=%.2f",
g_cfg.model.c_str(), g_cfg.base_url.c_str(),
g_cfg.max_tokens, g_cfg.temperature);
}
return 0;
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_configure exception: %s", e.what());
return -1;
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_configure unknown exception");
return -1;
}
return 0;
}
// ============================================================================
@@ -268,41 +276,55 @@ static dstalk_chat_result_t my_chat(
const char* user_input,
const char* /*tools_json*/)
{
dstalk_chat_result_t r = {};
r.ok = 0;
try {
dstalk_chat_result_t r = {};
r.ok = 0;
if (!g_http) {
r.error = g_host->strdup("http service not available");
if (!g_http) {
r.error = g_host->strdup("http service not available");
return r;
}
std::string scheme, host, port, target;
extract_host_port(g_cfg.base_url, scheme, host, port, target);
std::string target_path = target + "/v1/messages";
std::string body = build_request_json(history, history_len,
user_input ? user_input : "", false);
std::string headers_json = build_headers_json();
char* response_body = nullptr;
int status_code = 0;
int ret = g_http->post_json(
host.c_str(), port.c_str(), target_path.c_str(), body.c_str(),
headers_json.c_str(), &response_body, &status_code);
if (ret != 0) {
r.error = g_host->strdup("http request failed");
return r;
}
parse_response(response_body, status_code, r);
if (response_body) {
g_host->free(response_body);
}
return r;
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_chat exception: %s", e.what());
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = g_host ? g_host->strdup(e.what()) : nullptr;
return r;
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_chat unknown exception");
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = g_host ? g_host->strdup("unknown exception") : nullptr;
return r;
}
std::string scheme, host, port, target;
extract_host_port(g_cfg.base_url, scheme, host, port, target);
std::string target_path = target + "/v1/messages";
std::string body = build_request_json(history, history_len,
user_input ? user_input : "", false);
std::string headers_json = build_headers_json();
char* response_body = nullptr;
int status_code = 0;
int ret = g_http->post_json(
host.c_str(), port.c_str(), target_path.c_str(), body.c_str(),
headers_json.c_str(), &response_body, &status_code);
if (ret != 0) {
r.error = g_host->strdup("http request failed");
return r;
}
parse_response(response_body, status_code, r);
if (response_body) {
g_host->free(response_body);
}
return r;
}
// ============================================================================
@@ -320,29 +342,37 @@ struct StreamContext {
// 行回调
static int sse_line_callback(const char* line, void* userdata)
{
auto* ctx = static_cast<StreamContext*>(userdata);
if (!line || !line[0]) return 1; // 空行,继续
try {
auto* ctx = static_cast<StreamContext*>(userdata);
if (!line || !line[0]) return 1; // 空行,继续
std::string line_str(line);
std::string line_str(line);
// SSE 格式: "data: <json>"
if (line_str.rfind("data: ", 0) == 0) {
std::string data = line_str.substr(6);
std::string token;
if (parse_sse_data(data, token)) {
ctx->saw_data_line = true;
if (token.empty()) {
// message_stop
return 0;
}
ctx->accumulated += token;
if (ctx->user_cb) {
return ctx->user_cb(token.c_str(), ctx->userdata);
// SSE 格式: "data: <json>"
if (line_str.rfind("data: ", 0) == 0) {
std::string data = line_str.substr(6);
std::string token;
if (parse_sse_data(data, token)) {
ctx->saw_data_line = true;
if (token.empty()) {
// message_stop
return 0;
}
ctx->accumulated += token;
if (ctx->user_cb) {
return ctx->user_cb(token.c_str(), ctx->userdata);
}
}
}
// "event: ..." 行和其他 -> 忽略
return 1;
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] sse_line_callback exception: %s", e.what());
return 0;
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] sse_line_callback unknown exception");
return 0;
}
// "event: ..." 行和其他 -> 忽略
return 1;
}
static dstalk_chat_result_t my_chat_stream(
@@ -350,81 +380,95 @@ static dstalk_chat_result_t my_chat_stream(
const char* user_input,
dstalk_stream_cb cb, void* userdata)
{
dstalk_chat_result_t r = {};
r.ok = 0;
if (!g_http) {
r.error = g_host->strdup("http service not available");
return r;
}
std::string scheme, host, port, target;
extract_host_port(g_cfg.base_url, scheme, host, port, target);
std::string target_path = target + "/v1/messages";
std::string body = build_request_json(history, history_len,
user_input ? user_input : "", true);
std::string headers_json = build_headers_json();
StreamContext ctx;
ctx.host = g_host;
ctx.user_cb = cb;
ctx.userdata = userdata;
ctx.saw_data_line = false;
char* response_body = nullptr;
int status_code = 0;
int ret = g_http->post_stream(
host.c_str(), port.c_str(), target_path.c_str(), body.c_str(),
headers_json.c_str(),
sse_line_callback, &ctx,
&response_body, &status_code);
r.http_status = status_code;
// 检查错误状态
if (status_code < 200 || status_code >= 300) {
try {
dstalk_chat_result_t r = {};
r.ok = 0;
if (response_body && response_body[0]) {
try {
auto jv = json::parse(response_body);
auto obj = jv.as_object();
if (obj.contains("error")) {
auto err = obj["error"].as_object();
if (!g_http) {
r.error = g_host->strdup("http service not available");
return r;
}
std::string scheme, host, port, target;
extract_host_port(g_cfg.base_url, scheme, host, port, target);
std::string target_path = target + "/v1/messages";
std::string body = build_request_json(history, history_len,
user_input ? user_input : "", true);
std::string headers_json = build_headers_json();
StreamContext ctx;
ctx.host = g_host;
ctx.user_cb = cb;
ctx.userdata = userdata;
ctx.saw_data_line = false;
char* response_body = nullptr;
int status_code = 0;
int ret = g_http->post_stream(
host.c_str(), port.c_str(), target_path.c_str(), body.c_str(),
headers_json.c_str(),
sse_line_callback, &ctx,
&response_body, &status_code);
r.http_status = status_code;
// 检查错误状态
if (status_code < 200 || status_code >= 300) {
r.ok = 0;
if (response_body && response_body[0]) {
try {
auto jv = json::parse(response_body);
auto obj = jv.as_object();
if (obj.contains("error")) {
auto err = obj["error"].as_object();
r.error = g_host->strdup(
json::value_to<std::string>(err["message"]).c_str());
}
} catch (...) {}
}
if (!r.error) {
if (status_code <= 0)
r.error = g_host->strdup("transport error");
else
r.error = g_host->strdup(
json::value_to<std::string>(err["message"]).c_str());
}
} catch (...) {}
}
if (!r.error) {
if (status_code <= 0)
r.error = g_host->strdup("transport error");
else
r.error = g_host->strdup(
("HTTP " + std::to_string(status_code)).c_str());
("HTTP " + std::to_string(status_code)).c_str());
}
if (response_body) g_host->free(response_body);
r.content = nullptr;
r.tool_calls_json = nullptr;
return r;
}
if (response_body) g_host->free(response_body);
r.content = nullptr;
r.tool_calls_json = nullptr;
if (ctx.accumulated.empty() && !ctx.saw_data_line) {
r.ok = 0;
r.error = g_host->strdup("no content received");
r.content = nullptr;
r.tool_calls_json = nullptr;
} else {
r.ok = 1;
r.error = nullptr;
r.content = g_host->strdup(ctx.accumulated.c_str());
r.tool_calls_json = nullptr;
}
return r;
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_chat_stream exception: %s", e.what());
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = g_host ? g_host->strdup(e.what()) : nullptr;
return r;
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] my_chat_stream unknown exception");
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = g_host ? g_host->strdup("unknown exception") : nullptr;
return r;
}
if (response_body) g_host->free(response_body);
if (ctx.accumulated.empty() && !ctx.saw_data_line) {
r.ok = 0;
r.error = g_host->strdup("no content received");
r.content = nullptr;
r.tool_calls_json = nullptr;
} else {
r.ok = 1;
r.error = nullptr;
r.content = g_host->strdup(ctx.accumulated.c_str());
r.tool_calls_json = nullptr;
}
return r;
}
// ============================================================================
@@ -453,28 +497,42 @@ static dstalk_ai_service_t g_service = {
// ============================================================================
static int on_init(const dstalk_host_api_t* host)
{
g_host = host;
g_http = (dstalk_http_service_t*)host->query_service("http", 1);
g_config = (dstalk_config_service_t*)host->query_service("config", 1);
try {
g_host = host;
g_http = (dstalk_http_service_t*)host->query_service("http", 1);
g_config = (dstalk_config_service_t*)host->query_service("config", 1);
if (!g_http) {
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[anthropic] http service not found");
if (!g_http) {
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[anthropic] http service not found");
return -1;
}
if (g_host) g_host->log(DSTALK_LOG_INFO, "[anthropic] initializing Anthropic AI plugin");
return host->register_service("ai.anthropic", 1, &g_service);
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] on_init exception: %s", e.what());
return -1;
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] on_init unknown exception");
return -1;
}
if (g_host) g_host->log(DSTALK_LOG_INFO, "[anthropic] initializing Anthropic AI plugin");
return host->register_service("ai.anthropic", 1, &g_service);
}
static void on_shutdown()
{
if (g_host) g_host->log(DSTALK_LOG_INFO, "[anthropic] shutdown");
secure_zero(g_cfg.api_key.data(), g_cfg.api_key.size());
g_cfg.api_key.clear();
g_http = nullptr;
g_config = nullptr;
g_host = nullptr;
try {
if (g_host) g_host->log(DSTALK_LOG_INFO, "[anthropic] shutdown");
secure_zero(g_cfg.api_key.data(), g_cfg.api_key.size());
g_cfg.api_key.clear();
g_http = nullptr;
g_config = nullptr;
g_host = nullptr;
} catch (const std::exception& e) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] on_shutdown exception: %s", e.what());
} catch (...) {
if (g_host && g_host->log) g_host->log(DSTALK_LOG_ERROR, "[anthropic] on_shutdown unknown exception");
}
}
// ============================================================================