security: adopt the SDK roots-replacement (SEP-2322) so the wrong-repo write guard survives 2026-07-28 clients #428
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#759 epic: code and supply-chain security hardening
sixfold-space/madtea
Reference
sixfold-space/madtea#428
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Follow-up from #387 (2026-07-28 / go-sdk v1.7.0). This changes a security and isolation posture, so I want it settled deliberately rather than folded into routine SDK upkeep.
The degradation
SEP-2577 deprecates the MCP roots feature, and go-sdk v1.7.0 refuses server-initiated
roots/liston any session that negotiates >= 2026-07-28. madtea's wrong-repo write guard (callerRepoFromRootsin internal/mcp/repoguard.go - the guard exists because a PR once merged to the wrong repo) uses that roots round-trip as its structural caller-repo signal. On a 2026-07-28 session it now gets no signal, and the guard falls through to theMADTEA_CALLER_REPOenv hint alone, which is unset by default.Current exposure
None immediate. No current published client negotiates 2026-07-28 (the TypeScript conformance harness cannot), and every consumer today, Claude Code included, speaks 2025-11-25, where the roots cross-check still works. #387 preserved today's behavior for today's clients, documented the fork in repoguard.go, and pinned the roots test to a wire-level 2025-11-25 client. The change is fail-open into the guard's existing no-signal path, so it adds no false blocks. But the deprecation window is about 12 months, and once the primary client moves to 2026-07-28 the secondary wrong-repo protection stops functioning for callers that do not set the env hint.
Options
MADTEA_CALLER_REPOset by the harness for modern-client sessions, as a compensating control.Related: #387.
hexajon referenced this issue2026-09-08 20:22:25 +00:00
Decided: migrate the roots-derived signals to SEP-2322 within the deprecation window, not now.
State of play: roots is deprecated (SEP-2577, June 2026) but fully functional on a rolling ~12-month window, into 2027. SEP-2322 (Multi Round-Trip Requests / InputRequests) is the successor for the roots/list request. No published client negotiates SEP-2322 yet, and Claude Code still implements roots/list and roots/list_changed today.
Decision:
The wrong-repo guard degrades fail-open on no signal (accident-detector, ADR 0007), so there is no security-boundary regression during the transition; explicit owner_repo / dir targeting and MADTEA_CALLER_REPO stay available. Clearing needs-decision. Related: #416 (uses roots), #412 (MCP-spec adoption epic).
A correction to the description above, from reading the code today.
The per-call wrong-repo guard does not use roots.
consolidated_tool.gocallsguardRepoResolutionForCall(ctx, nil, ...)with a nil session, socallerRepoFromRootsreturns no signal there and the guard relies on theMADTEA_CALLER_REPOhint alone.The one place the roots answer matters is
guardLocalGitTarget(internal/mcp/multirepo.go). It guards the local-git tools (add, commit, push, pull, branch create and delete, finish) when a call passes nodir=:dir=.So migrating off roots affects nested-repo workspaces (dir-less local-git calls are refused until
dir=orMADTEA_CALLER_REPOis given) and the root-versus-server mismatch check. The new gopls gate leg in #624 raises this again, because gopls flags the deprecated call.Update from reading the code today. The premise in the description above, that no client negotiates 2026-07-28 and Claude Code speaks 2025-11-25, no longer holds. A 2026-07-28 session now rejects a tool result that omits
resultType(fixed in #740), which only happens when the negotiated revision is 2026-07-28. So the primary client is on 2026-07-28, go-sdk refuses server-initiatedroots/liston that session, andguardLocalGitTarget's roots cross-check has no signal there. It degrades fail-open (ADR 0007): a dir-less local-git call in a nested-repo workspace falls back to needingdir=orMADTEA_CALLER_REPO. No trust-boundary regression, and the narrow impact the 2026-09-24 correction described.The successor is ready in the SDK. The go-sdk implements SEP-2322 on both ends: a handler returns InputRequests as an
InputRequiredResult, and the client fulfills aListRootsRequestinput request and retries. So migrating the roots-derived signal is now viable, not against a surface no client speaks.It is not a drop-in. On a 2026-07-28 session the server cannot request roots synchronously; the SEP-2322 shape is a result that asks for input and a client-driven retry. So
guardLocalGitTargetchanges from a synchronous roots call into that two-round-trip flow. I will do that migration as its own focused change, linked here, rather than fold it into unrelated work. It connects to #624 (gopls flags the deprecated call) and #416 (also reads roots).