From 7455abd9e0121116fc002029d709a7cf410b4195 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Wed, 11 Feb 2026 18:08:07 -0800 Subject: Restrict content-type on POST requests to compact binary or JSON --- src/zen/cmds/projectstore_cmd.cpp | 4 +++- src/zenserver-test/projectstore-tests.cpp | 8 +++++--- src/zenserver-test/zenserver-test.cpp | 2 +- src/zenserver/storage/projectstore/httpprojectstore.cpp | 17 +++++++++++++++-- 4 files changed, 24 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp index 4885fd363..519b68126 100644 --- a/src/zen/cmds/projectstore_cmd.cpp +++ b/src/zen/cmds/projectstore_cmd.cpp @@ -809,6 +809,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg } IoBuffer OplogPayload; + OplogPayload.SetContentType(ZenContentType::kCbObject); if (!m_GcPath.empty()) { OplogPayload = MakeCbObjectPayload([&](CbObjectWriter& Writer) { Writer.AddString("gcpath"sv, m_GcPath); }); @@ -1141,7 +1142,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg if (CreateOplog) { ZEN_CONSOLE_WARN("Creating zen remote oplog '{}/{}'", m_ZenProjectName, m_ZenOplogName); - if (HttpClient::Response Result = TargetHttp.Post(Url); !Result) + if (HttpClient::Response Result = TargetHttp.Post(Url, IoBuffer(), ZenContentType::kCbObject); !Result) { Result.ThrowError("failed creating zen remote oplog"sv); } @@ -1608,6 +1609,7 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg if (CreateOplog) { IoBuffer OplogPayload; + OplogPayload.SetContentType(ZenContentType::kCbObject); if (!m_GcPath.empty()) { OplogPayload = MakeCbObjectPayload([&](CbObjectWriter& Writer) { Writer.AddString("gcpath"sv, m_GcPath); }); diff --git a/src/zenserver-test/projectstore-tests.cpp b/src/zenserver-test/projectstore-tests.cpp index 735aef159..ead062628 100644 --- a/src/zenserver-test/projectstore-tests.cpp +++ b/src/zenserver-test/projectstore-tests.cpp @@ -86,7 +86,7 @@ TEST_CASE("project.basic") HttpClient Http{BaseUri}; { - auto Response = Http.Post(""sv); + auto Response = Http.Post(""sv, IoBuffer{}, ZenContentType::kCbObject); CHECK(Response.StatusCode == HttpResponseCode::Created); } @@ -441,7 +441,8 @@ TEST_CASE("project.remote") auto MakeOplog = [](std::string_view UrlBase, std::string_view ProjectName, std::string_view OplogName) { HttpClient Http{UrlBase}; - HttpClient::Response Response = Http.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName), IoBuffer{}); + HttpClient::Response Response = + Http.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName), IoBuffer{}, ZenContentType::kCbObject); CHECK(Response); }; @@ -891,7 +892,8 @@ TEST_CASE("project.rpcappendop") }; auto MakeOplog = [](HttpClient& Client, std::string_view ProjectName, std::string_view OplogName) { - HttpClient::Response Response = Client.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName)); + HttpClient::Response Response = + Client.Post(fmt::format("/prj/{}/oplog/{}", ProjectName, OplogName), IoBuffer{}, ZenContentType::kCbObject); CHECK_MESSAGE(Response.IsSuccess(), Response.ErrorMessage("")); }; auto GetOplog = [](HttpClient& Client, std::string_view ProjectName, std::string_view OplogName) { diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp index 9a42bb73d..61ce4cd17 100644 --- a/src/zenserver-test/zenserver-test.cpp +++ b/src/zenserver-test/zenserver-test.cpp @@ -86,7 +86,7 @@ main(int argc, char** argv) zen::logging::InitializeLogging(); - zen::logging::SetLogLevel(zen::logging::level::Debug); + // zen::logging::SetLogLevel(zen::logging::level::Debug); spdlog::set_formatter(std::make_unique("test", std::chrono::system_clock::now())); std::filesystem::path ProgramBaseDir = GetRunningExecutablePath().parent_path(); diff --git a/src/zenserver/storage/projectstore/httpprojectstore.cpp b/src/zenserver/storage/projectstore/httpprojectstore.cpp index 416e2ed69..575bf4354 100644 --- a/src/zenserver/storage/projectstore/httpprojectstore.cpp +++ b/src/zenserver/storage/projectstore/httpprojectstore.cpp @@ -1866,6 +1866,14 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req) { return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); } + + if (HttpReq.RequestContentType() == HttpContentType::kText || + HttpReq.RequestContentType() == HttpContentType::kUnknownContentType) + { + m_ProjectStats.BadRequestCount++; + return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type"); + } + std::filesystem::path OplogMarkerPath; if (CbObject Params = HttpReq.ReadPayloadObject()) { @@ -2156,6 +2164,13 @@ HttpProjectService::HandleProjectRequest(HttpRouterRequest& Req) return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); } + if (HttpReq.RequestContentType() == HttpContentType::kText || + HttpReq.RequestContentType() == HttpContentType::kUnknownContentType) + { + m_ProjectStats.BadRequestCount++; + return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type"); + } + CbValidateError ValidateResult; if (CbObject Params = ValidateAndReadCompactBinaryObject(HttpReq.ReadPayload(), ValidateResult); ValidateResult == CbValidateError::None) @@ -2568,8 +2583,6 @@ HttpProjectService::HandleRpcRequest(HttpRouterRequest& Req) switch (PayloadContentType) { case HttpContentType::kJSON: - case HttpContentType::kUnknownContentType: - case HttpContentType::kText: { std::string JsonText(reinterpret_cast(Payload.GetData()), Payload.GetSize()); Cb = LoadCompactBinaryFromJson(JsonText).AsObject(); -- cgit v1.2.3 From ecebb4f3775e6d1ed42f97d7d4a1fe5954663695 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Thu, 19 Feb 2026 16:26:18 -0800 Subject: Restore debug logging in zenserver tests --- src/zenserver-test/zenserver-test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp index 61ce4cd17..9a42bb73d 100644 --- a/src/zenserver-test/zenserver-test.cpp +++ b/src/zenserver-test/zenserver-test.cpp @@ -86,7 +86,7 @@ main(int argc, char** argv) zen::logging::InitializeLogging(); - // zen::logging::SetLogLevel(zen::logging::level::Debug); + zen::logging::SetLogLevel(zen::logging::level::Debug); spdlog::set_formatter(std::make_unique("test", std::chrono::system_clock::now())); std::filesystem::path ProgramBaseDir = GetRunningExecutablePath().parent_path(); -- cgit v1.2.3 From 1cd70d1e875c2331d8a3c57aa8b0fd7267a63973 Mon Sep 17 00:00:00 2001 From: Liam Mitchell Date: Wed, 4 Mar 2026 17:52:54 -0800 Subject: Allow requests with invalid content-types unless specified in command line or config --- .../storage/projectstore/httpprojectstore.cpp | 21 ++++++++++++++++----- .../storage/projectstore/httpprojectstore.h | 4 +++- src/zenserver/storage/storageconfig.cpp | 7 +++++++ src/zenserver/storage/storageconfig.h | 1 + src/zenserver/storage/zenstorageserver.cpp | 10 ++++++++-- 5 files changed, 35 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/zenserver/storage/projectstore/httpprojectstore.cpp b/src/zenserver/storage/projectstore/httpprojectstore.cpp index 575bf4354..fe32fa15b 100644 --- a/src/zenserver/storage/projectstore/httpprojectstore.cpp +++ b/src/zenserver/storage/projectstore/httpprojectstore.cpp @@ -535,7 +535,8 @@ HttpProjectService::HttpProjectService(CidStore& Store, HttpStatsService& StatsService, AuthMgr& AuthMgr, OpenProcessCache& InOpenProcessCache, - JobQueue& InJobQueue) + JobQueue& InJobQueue, + bool InRestrictContentTypes) : m_Log(logging::Get("project")) , m_CidStore(Store) , m_ProjectStore(Projects) @@ -544,6 +545,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, , m_AuthMgr(AuthMgr) , m_OpenProcessCache(InOpenProcessCache) , m_JobQueue(InJobQueue) +, m_RestrictContentTypes(InRestrictContentTypes) { ZEN_MEMSCOPE(GetProjectHttpTag()); @@ -1867,8 +1869,8 @@ HttpProjectService::HandleOpLogRequest(HttpRouterRequest& Req) return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); } - if (HttpReq.RequestContentType() == HttpContentType::kText || - HttpReq.RequestContentType() == HttpContentType::kUnknownContentType) + if (m_RestrictContentTypes && (HttpReq.RequestContentType() == HttpContentType::kText || + HttpReq.RequestContentType() == HttpContentType::kUnknownContentType)) { m_ProjectStats.BadRequestCount++; return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type"); @@ -2164,8 +2166,8 @@ HttpProjectService::HandleProjectRequest(HttpRouterRequest& Req) return HttpReq.WriteResponse(HttpResponseCode::InsufficientStorage); } - if (HttpReq.RequestContentType() == HttpContentType::kText || - HttpReq.RequestContentType() == HttpContentType::kUnknownContentType) + if (m_RestrictContentTypes && (HttpReq.RequestContentType() == HttpContentType::kText || + HttpReq.RequestContentType() == HttpContentType::kUnknownContentType)) { m_ProjectStats.BadRequestCount++; return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type"); @@ -2582,8 +2584,17 @@ HttpProjectService::HandleRpcRequest(HttpRouterRequest& Req) CbObject Cb; switch (PayloadContentType) { + case HttpContentType::kText: + case HttpContentType::kUnknownContentType: case HttpContentType::kJSON: { + if (m_RestrictContentTypes && + (PayloadContentType == HttpContentType::kText || PayloadContentType == HttpContentType::kUnknownContentType)) + { + m_ProjectStats.BadRequestCount++; + return HttpReq.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid request content type"); + } + std::string JsonText(reinterpret_cast(Payload.GetData()), Payload.GetSize()); Cb = LoadCompactBinaryFromJson(JsonText).AsObject(); if (!Cb) diff --git a/src/zenserver/storage/projectstore/httpprojectstore.h b/src/zenserver/storage/projectstore/httpprojectstore.h index b742102a5..1d71329b1 100644 --- a/src/zenserver/storage/projectstore/httpprojectstore.h +++ b/src/zenserver/storage/projectstore/httpprojectstore.h @@ -44,7 +44,8 @@ public: HttpStatsService& StatsService, AuthMgr& AuthMgr, OpenProcessCache& InOpenProcessCache, - JobQueue& InJobQueue); + JobQueue& InJobQueue, + bool InRestrictContentTypes); ~HttpProjectService(); virtual const char* BaseUri() const override; @@ -109,6 +110,7 @@ private: metrics::OperationTiming m_HttpRequests; RwLock m_ThreadWorkersLock; Ref m_ThreadWorkers; + bool m_RestrictContentTypes; Ref GetThreadWorkers(bool BoostWorkers, bool SingleThreaded); }; diff --git a/src/zenserver/storage/storageconfig.cpp b/src/zenserver/storage/storageconfig.cpp index 0f8ab1e98..99d0f89d7 100644 --- a/src/zenserver/storage/storageconfig.cpp +++ b/src/zenserver/storage/storageconfig.cpp @@ -496,6 +496,7 @@ ZenStorageServerConfigurator::AddConfigOptions(LuaConfig::Options& LuaOptions) LuaOptions.AddOption("security.encryptionaeskey"sv, ServerOptions.EncryptionKey, "encryption-aes-key"sv); LuaOptions.AddOption("security.encryptionaesiv"sv, ServerOptions.EncryptionIV, "encryption-aes-iv"sv); LuaOptions.AddOption("security.openidproviders"sv, ServerOptions.AuthConfig); + LuaOptions.AddOption("security.restrictcontenttypes"sv, ServerOptions.RestrictContentTypes, "restrict-content-types"sv); ////// workspaces LuaOptions.AddOption("workspaces.enabled"sv, ServerOptions.WorksSpacesConfig.Enabled, "workspaces-enabled"sv); @@ -649,6 +650,12 @@ ZenStorageServerCmdLineOptions::AddSecurityOptions(cxxopts::Options& options, Ze options.add_option("security", "", "openid-provider-url", "Open ID provider URL", cxxopts::value(OpenIdProviderUrl), ""); options.add_option("security", "", "openid-client-id", "Open ID client ID", cxxopts::value(OpenIdClientId), ""); + options.add_option("security", + "", + "restrict-content-types", + "Restrict content-type in requests to content-types that are not allowed in CORS simple requests", + cxxopts::value(ServerOptions.RestrictContentTypes), + ""); } void diff --git a/src/zenserver/storage/storageconfig.h b/src/zenserver/storage/storageconfig.h index d59d05cf6..bc2dc78c9 100644 --- a/src/zenserver/storage/storageconfig.h +++ b/src/zenserver/storage/storageconfig.h @@ -157,6 +157,7 @@ struct ZenStorageServerConfig : public ZenServerConfig std::filesystem::path PluginsConfigFile; // Path to plugins config file bool ObjectStoreEnabled = false; std::string ScrubOptions; + bool RestrictContentTypes = false; }; struct ZenStorageServerCmdLineOptions diff --git a/src/zenserver/storage/zenstorageserver.cpp b/src/zenserver/storage/zenstorageserver.cpp index b2cae6482..ea05bd155 100644 --- a/src/zenserver/storage/zenstorageserver.cpp +++ b/src/zenserver/storage/zenstorageserver.cpp @@ -205,8 +205,14 @@ ZenStorageServer::InitializeServices(const ZenStorageServerConfig& ServerOptions m_OpenProcessCache = std::make_unique(); m_ProjectStore = new ProjectStore(*m_CidStore, m_DataRoot / "projects", m_GcManager, ProjectStore::Configuration{}); - m_HttpProjectService.reset( - new HttpProjectService{*m_CidStore, m_ProjectStore, m_StatusService, m_StatsService, *m_AuthMgr, *m_OpenProcessCache, *m_JobQueue}); + m_HttpProjectService.reset(new HttpProjectService{*m_CidStore, + m_ProjectStore, + m_StatusService, + m_StatsService, + *m_AuthMgr, + *m_OpenProcessCache, + *m_JobQueue, + ServerOptions.RestrictContentTypes}); if (ServerOptions.WorksSpacesConfig.Enabled) { -- cgit v1.2.3