Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,43 @@ void ClearProviders(Config& config) {
config.model.decoder.session_options.providers.clear();
}

// Escape a string for safe embedding inside a JSON string literal. Prevents JSON
// injection when caller-supplied values are concatenated into a JSON document that
// will subsequently be parsed (e.g. in SetProviderOption below). Handles the
// mandatory JSON escapes: quote, backslash, and the C0 control-character shortcuts
// that the local JSON parser understands (\b \f \n \r \t).
//
// Other C0 control characters (< 0x20) have no shortcut and would require a
// \uXXXX escape, which the local JSON parser in src/json.cpp does not support
// (it throws "Unsupported uXXXX code used"). Since provider option names and
// values are configuration strings that are not expected to contain raw
// control characters, reject them here with a clear error rather than
// producing JSON that the parser cannot consume.
static std::string EscapeJsonString(std::string_view s) {
std::string result;
result.reserve(s.size());
for (char c : s) {
switch (c) {
case '"': result += "\\\""; break;
case '\\': result += "\\\\"; break;
case '\b': result += "\\b"; break;
case '\f': result += "\\f"; break;
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
throw std::runtime_error(
"Unsupported control character in provider option string (code " +
std::to_string(static_cast<unsigned char>(c)) + ")");
}
result += c;
break;
}
}
return result;
}

void SetProviderOption(Config& config, std::string_view provider_name, std::string_view option_name, std::string_view option_value) {
// Normalize the provider name once
auto normalized_provider = NormalizeProviderName(provider_name);
Expand All @@ -1421,10 +1458,14 @@ void SetProviderOption(Config& config, std::string_view provider_name, std::stri
}
}

// JSON-escape all caller-supplied string fragments before concatenating them into the
// JSON document. Without escaping, quote/backslash characters in provider_name,
// option_name, or option_value would let a caller inject arbitrary JSON structure
// (sibling keys, new provider entries, etc.) into the parsed configuration.
std::ostringstream json;
json << R"({")" << provider_name << R"(":{)";
json << R"({")" << EscapeJsonString(provider_name) << R"(":{)";
if (!option_name.empty()) {
json << R"(")" << option_name << R"(":")" << option_value << R"(")";
json << R"(")" << EscapeJsonString(option_name) << R"(":")" << EscapeJsonString(option_value) << R"(")";
}
json << R"(}})";

Expand Down
Loading