0.158.2

Fixed

  • MCP OAuth: закрыты SSRF-вектор в token-exchange и хардкод redirect_uri (ADR docs/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-server allow_local/tls_skip_verify/tls_ca), с client-level таймаутом 15 с (token-exchange — короткий запрос, не SSE long-poll). В MCPHandler добавлен guardGetter (setter SetSSRFGuard, контракт 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. Helper MCPHandler.callbackURL(r) с приоритетом: env TAIGACLAW_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 review0 валидных замечаний.