fleetd #518: FleetMcp caller resolution is an explicit choice, and tested for real #524
Reference in New Issue
Block a user
Delete Branch "worker/518-fleetmcp-resolver-wiring-8ef96c-1"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #518 — "Nothing tests that FleetMcp uses the CallerResolver".
The defect
FleetMcp'scontextExtractorpicked its caller-resolution path offcallers == null:enforced ->
callers.resolve(...), legacy -> a second, separately-maintained heuristic(
legacyPrincipal). Nothing ever drove a real MCP request through that closure, so a mutantthat replaced the whole decision with an unconditional
legacyPrincipal(...)call passedthe full suite (1696 tests) — the closure was an unexercised claim, not a control.
Part 1 — the legacy choice is now explicit at construction
callers(CallerResolver) is now a required, non-null constructor parameter, always.FleetMcp.AuthorizationModeenum (ENFORCED/UNENFORCED) is a requiredconstructor parameter with no default, replacing the
callers == nullidiom forwhether
denyForenforces the CB-505 policy table at all.legacyPrincipalis deleted. There is now exactly one resolution path(
callers.resolve(remoteAddr, remotePort, authorizationHeader)), used unconditionally —even under
UNENFORCED, somarkSpawnedMemberPresent/recordPrimarySingletonstill see areal identity.
The mutation is impossible to write, not merely caught. I reproduced the ticket's exact
mutation (replace the resolution line with
legacyPrincipal(identity, req.getRemoteAddr(), req.getRemotePort())) against the fixed code and it fails to compile:I verified the mutation actually applied first (grep for the mutant text present, the
original text gone, and a
grep -nre-read of the line), watchedmvn -o compilefail withexit 1, then restored the file from a saved copy and confirmed by
shasum -a 256that therestored file exactly matches the pre-mutation (fixed) content, then ran a green control
build (
mvn -o compileexit 0, then the fullmvn -o clean install).Part 2 — a test that actually drives the closure
FleetMcpContextExtractorTest(new) boots the realHttpServletStreamableServerTransportProvideron a real embedded Jetty server, and drives itwith a real MCP client (
io.modelcontextprotocol.sdkstreamable-HTTP client) over actualHTTP. It uses
CallerResolverin token mode with a peer-pid lookup that never resolves to aworker pane, so the only way
fleet_whoamican come back"role":"primary"is if thecontextExtractorclosure really calledcallers.resolve(...)and read theAuthorizationheader — a behaviour the deleted
legacyPrincipalheuristic never had. A second call with nocredential at all is asserted refused (ANONYMOUS). This is the one test that would have gone
red under the original mutation; every other existing test calls
denyFor(Principal, ...)with a hand-built
Principaland never touches the transport at all.Must-keep test
FleetMcpAuthzTest.theLegacyConstructorLeavesTheGateOpenstill expresses the same thing(authorization can be turned off), now via
mcp(false)passingAuthorizationMode.UNENFORCEDexplicitly instead of a nullCallerResolver. Adapted, notdeleted.
FleetMcpAuthzTest.legacyPrincipalIsAnonymousNotPrimaryForAnUnresolvedCallertested the now-deleted
legacyPrincipalmethod directly (fleetd #509's fix). Since there is no longer asecond heuristic, I renamed/adapted it to
anUnresolvedNonLoopbackCallerIsAnonymousUnderTheOneRealResolver, which proves the sameproperty (an unresolved, non-loopback caller earns no authority) against the one real
CallerResolvernow in use — the same propertyCallerResolverTest .aNonLoopbackCallerIsNeverThePrimaryUnderLoopbackTrustalready independently proves on thatclass.
Other call sites updated
Fleetd.java(production wiring): passesAuthorizationMode.ENFORCED— production neverpassed a null
CallerResolver, so this is a no-behaviour-change wiring update only.FleetMcpHandoverTest.java: same,AuthorizationMode.ENFORCED(it always passed a realresolver already).
Also — same shape found elsewhere (NOT fixed, per ticket scope)
FleetMcp.principalFrom(Object role, String terminal, long pid, String name)(around line577 pre-change):
if (role == null) { return terminal != null ? worker : Principal.primary(pid); }— a missing/omitted
rolesilently promotes an unresolved caller to primary, the same"unsafe branch reached by omission" shape as the defect this ticket fixes, and arguably
worse (defaults to full authority, not anonymous).
dev.ltms.fleet.rest.FleetApp.java:85:private final CallerResolver auth; // CB-501: null -> authz not enforced (legacy behaviour)— the REST-side sibling of the exact field thisticket removed from
FleetMcp.Acceptance
mvn -f fleetd/pom.xml clean install— exit 0.Tests run: 1697, Failures: 0, Errors: 0, Skipped: 0/BUILD SUCCESS(1696 baseline + 1 new test).grep -nre-read of the line — all run and shown in the session transcript.shasum -a 256matches the saved pre-mutation (fixed) copy exactly, and agreen control build followed. Note:
FleetMcp.java's hash necessarily differs from theticket's original pristine hash (
517279e6...), because Part 1 is an intentional,permanent production-code change to that file — the byte-identical check applies to
undoing the temporary mutation test, not to reverting the fix itself.
Caveats for review
mvnoutput only, shownabove and in the PR history.
FleetMcpContextExtractorTeststarts a real Jetty server on an ephemeral port (new Server(0)) and a real MCP HTTP client — it is the first test in this codebase to do so forFleetMcp; it adds ~0.3-0.9s to the suite.FleetMcp's contextExtractor picked its principal-resolution path off `callers == null`, so "authorization off" also silently swapped in a second, untested identity heuristic (legacyPrincipal). Nothing drove that closure through a real MCP request, so the whole wiring was an unexercised claim. - callers (CallerResolver) is now required, never null. - A new AuthorizationMode enum (ENFORCED/UNENFORCED) is a required constructor parameter with no default, replacing the null-means-legacy idiom for whether denyFor enforces at all. - legacyPrincipal is deleted: there is exactly one resolution path now (callers.resolve(...)), so the mutation that swapped it for an unconditional legacy call no longer compiles ("cannot find symbol: method legacyPrincipal"). - FleetMcpContextExtractorTest boots the real transport on a real Jetty server and drives it with a real MCP client, proving fleet_whoami's resolved role comes from CallerResolver's token check. - Adapted FleetMcpAuthzTest/FleetMcpHandoverTest call sites; theLegacyConstructorLeavesTheGateOpen keeps its meaning under the new AuthorizationMode.UNENFORCED value.