0.158.2
Fixed
- MCP OAuth: закрыты SSRF-вектор в token-exchange и хардкод
redirect_uri(ADRdocs/adr/2026-07-02-mcp-full-coverage.md, Фаза 0, ). Два дефекта browser-OAuth-флоу MCP (internal/server/handler/mcp.go): (1)OAuthCallbackделал POST на<base>/tokenчерезhttp.DefaultClientбез SSRF guard — злонамеренный MCP-сервер с приватным IP в token-endpoint давал SSRF во внутреннюю сеть (тот же класс уязвимости, что уже закрыт для SSE/HTTP-транспорта); (2)redirect_uriбыл захардкоженhttp://localhost:14888/...и ломался при нестандартном порту, https, reverse-proxy, remote-deploy. - SSRF-safe token-exchange. Новая экспортируемая функция
mcp.OAuthExchangeClient(getter, srv)(internal/mcp/http.go) строит*http.Clientчерез тот же механизм, что MCP-транспорт (security.NewSafeHTTPClientFromGetterWithTLS+ per-serverallow_local/tls_skip_verify/tls_ca), с client-level таймаутом 15 с (token-exchange — короткий запрос, не SSE long-poll). ВMCPHandlerдобавленguardGetter(setterSetSSRFGuard, контракт set-once-before-serve по образцуSetManager; прокидывается изmain.goчерезproviders.SSRFGuard). Если guard не задан — callback fail-closed (503), не диалим потенциально злонамеренный token-endpoint. Дополнительно: scheme-проверкаsrv.URL(http/https) вOAuthAuthorize(defence-in-depth противfile:///javascript:в authURL, открываемом браузером),urlHostLog-редакция token-endpoint вslog(без path/query). - Динамический
redirect_uri. HelperMCPHandler.callbackURL(r)с приоритетом: envTAIGACLAW_PUBLIC_URL(новая переменная, для reverse-proxy/production-deploy; валидируется — только абсолютный http/https URL с host, иначе игнорируется с warning) →X-Forwarded-Proto+X-Forwarded-HostчерезHeader.Values(leftmost-инстанс/leftmost-token, корректно для RFC 7239 multi-instance) →r.Host(scheme изr.TLS) → fail-closed (пустой результат → 400 в authorize с подсказкой настроитьTAIGACLAW_PUBLIC_URL/прокси). Значение сохраняется вpendingOAuth.RedirectURIприauthorizeи переиспользуется вcallback— гарантия совпадения (AS строго сверяетredirect_uriмежду двумя запросами); при legacy-fallback логируется warning о вероятномredirect_uri_mismatch. - URL-кодирование параметров.
redirect_uri/scope/code/code_verifier/client_idтеперьurl.QueryEscape-ятся (раньшеscopeс пробелом уходил сырым, а&/=в code ломали form-body). - Безопасность (отложено в Фазу 1): полный trusted-proxy-gate для
X-Forwarded-*(honor только приr.RemoteAddrв allowlist) — отложен, т.к./oauth/authorizeуже защищёнRequireGlobalAdmin(спуфинг доступен только админам), а полноценный OAuth-стек Фазы 1 получит явный операторский конфиг. Обоснование зафиксировано в комментарииcallbackURL. - Проверки:
go test ./internal/...— зелёные; новые тесты:TestOAuthExchangeClient_{BlocksLoopback,AllowsLoopbackWithAllowLocal,HasTimeout}(SSRF: строгий guard блокирует loopback,allow_localразрешает),TestCallbackURL(7 сценариев),TestCallbackURL_{RejectsInvalidPublicURL,EmptyHostFailClosed,AcceptsValidPublicURL},TestLeftmostHeader,TestUrlScheme,TestOAuthCallback_FailClosedWithoutGuard(503 без guard — security-критичный путь); циклgo vet+golangci-lint(noctx/errcheck/revive/gofmt) — чисто;make build-cross— проходит. - Код-ревью: цикл
ocr review(9 замечаний) — валидные устранены (fallback-docstring/fail-closed, валидация public URL,Header.Values, scheme-проверкаsrv.URL, log-redaction, test-coverage gap, mixed-language comments → English), 2 обоснованно отклонены (trusted-proxy-gate → Фаза 1; data-race наguardGetter→ консистентность с существующимSetManager, контракт set-once-before-serve). Повторныйocr review— 0 валидных замечаний.