Skip to content

Commit 4619687

Browse files
committed
fix(ci): Windows link mismatch + auth-context param leak (#22)
- mcp_authorization_policy.hpp forward-declared EndpointConfig as `class` while it is actually a `struct`. MSVC mangles the struct/class keyword into function symbol names, so the resulting `MCPAuthorizationPolicy::authorize` symbol seen at mcp_tool_handler's call site (struct, U-prefix) never matched the one emitted from mcp_authorization_policy.cpp (class, V-prefix), producing LNK2019 on Windows release while Itanium ABI builds linked cleanly. Fixed by changing the forward decl to `struct`. - RequestValidator::validateRequestFields now skips the reserved `__auth_*` prefix used by APIServer to inject auth context. Before this fix, write endpoints with validate_before_write enabled rejected every authenticated request as containing 5 phantom unknown fields (__auth_username/_email/_roles/_type/_authenticated), surfacing as the test_write_operations integration regressions on main. Unit-tested both the positive case (auth-prefix lets through) and the negative case (a key merely containing __auth_ is still rejected).
1 parent c09aaa0 commit 4619687

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

src/include/mcp_authorization_policy.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace flapi {
77

8-
class EndpointConfig;
8+
struct EndpointConfig;
99

1010
class MCPAuthorizationPolicy {
1111
public:

src/request_validator.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,14 @@ std::vector<ValidationError> RequestValidator::validateRequestFields(
311311
knownFields.insert("offset");
312312
knownFields.insert("limit");
313313

314-
// Check each parameter against known fields
314+
// Check each parameter against known fields. The `__auth_*` prefix is
315+
// reserved for the auth-context injection performed by APIServer; those
316+
// keys are not user input and must not be flagged as unknown.
317+
const std::string kAuthReservedPrefix = "__auth_";
315318
for (const auto& param : params) {
319+
if (param.first.rfind(kAuthReservedPrefix, 0) == 0) {
320+
continue;
321+
}
316322
if (knownFields.find(param.first) == knownFields.end()) {
317323
errors.push_back({
318324
param.first,

test/cpp/request_validator_test.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,4 +723,33 @@ TEST_CASE("RequestValidator: validateRequestFields", "[request_validator]") {
723723
REQUIRE(hasUnknownParam);
724724
REQUIRE(hasInvalidField);
725725
}
726+
727+
SECTION("Reserved __auth_* injection keys are not flagged as unknown") {
728+
// APIServer injects auth-context fields under the reserved __auth_*
729+
// prefix before validation. They are not user input, so the unknown-
730+
// parameter check must let them through silently. Without this skip,
731+
// every authenticated write request would 400 with phantom errors.
732+
std::map<std::string, std::string> params = {
733+
{"name", "John"},
734+
{"__auth_username", "alice"},
735+
{"__auth_email", "alice@example.com"},
736+
{"__auth_roles", "admin,analyst"},
737+
{"__auth_type", "jwt"},
738+
{"__auth_authenticated", "true"}
739+
};
740+
auto errors = validator.validateRequestFields(requestFields, params);
741+
REQUIRE(errors.empty());
742+
}
743+
744+
SECTION("Keys merely containing __auth_ are still flagged if not a prefix") {
745+
// The skip is prefix-based, so a key like "user___auth_token" that
746+
// contains the marker but does not start with it must still be
747+
// rejected — it could otherwise be smuggled past as a phantom field.
748+
std::map<std::string, std::string> params = {
749+
{"user___auth_token", "value"}
750+
};
751+
auto errors = validator.validateRequestFields(requestFields, params);
752+
REQUIRE(errors.size() == 1);
753+
REQUIRE(errors[0].fieldName == "user___auth_token");
754+
}
726755
}

0 commit comments

Comments
 (0)