0.134.46

Security (C-M-5 — Embedder re-embed pipeline, частичный фикс)

Реализован фундамент для переиндексации эмбеддингов при смене embedder’а с разной размерностью. Раньше при dimension mismatch SwappableEmbedder.Swap возвращал 409 без возможности продолжить. Теперь инфраструктура (Store + Reindexer) готова — админ сможет запустить фоновую переиндексацию, которая обновит все memory_facts и memory_chunks.

  • Миграция 077_reindex_jobs.up.sql — таблица reindex_jobs (id, kind, old_dim, new_dim, old_model, new_model, status, processed, total, started_at, finished_at, error, created_at) + UNIQUE INDEX uniq_active_reindex_job гарантирующий только один active job одновременно.
  • internal/store/store.goReindexJob struct + 7 новых методов Store: CreateReindexJob, GetActiveReindexJob, GetReindexJob, UpdateReindexJobProgress, UpdateReindexJobStatus, ListReindexJobs, EnsureVectorDimensions.
  • internal/store/sqlite.go — все 7 методов + UpdateChunkEmbedding + ListChunksNeedingReembed + IndexChunkUpdateVector (HNSW reindex).
  • internal/store/postgres.go — все 7 методов + UpdateChunkEmbedding
  • ListChunksNeedingReembed (использует существующий vectorToPG). EnsureVectorDimensions уже существовал.
  • internal/store/dual.go — passthrough-методы.
  • internal/embeddings/reindex.goReindexer с Start(kind), Cancel(), фоновой goroutine, батчами по 100, проверкой ctx.Err() на каждой итерации. ErrAlreadyRunning защищает от дублей.
  • internal/embeddings/reindex_test.go — 3 теста: Start creates job, DoubleStartFails, ProcessesFacts (создаёт факт → reindex → проверяет что mockProvider был вызван).

Planned (admin HTTP endpoints + WS events)

Полная интеграция в cmd/taigaclaw/main.go (swappable + reindexer wiring) и HTTP endpoint’ы (POST /api/v1/admin/embeddings/reindex, GET /status, POST /cancel) оставлены на следующий PR — сейчас инфраструктура (Store + Reindexer) готова и покрыта тестами.

Security (H-S-4, H-S-11 — file ops + IP fingerprint)

  • internal/server/handler/file.go — добавлен safeRemove(workspaceDir, target): централизованный helper для удаления файлов/папок с защитой от path traversal (isPathInWorkspace), symlink-атак (Lstat Mode), и race-атак через подмену каталога symlink-ом между Lstat и RemoveAll (EvalSymlinks + повторная проверка). Используется вместо прямого os.Remove/os.RemoveAll в handler-ах. Учитывает macOS /var/private/var (workspace и target резолвятся одинаково).
  • internal/server/handler/safe_remove_test.go — 5 тестов: file inside, dir recursive, path outside (отказ), symlink (отказ), non-existent (no-op).
  • Миграция 076_oauth_ip_fingerprint — колонка ip_fingerprint TEXT в oauth_sessions. NULL по умолчанию (backward-compat).
  • internal/store/store.goOAuthSession.IPFingerprint string поле. Документировано поведение: IPv4 = /24, IPv6 = /64.
  • internal/store/{sqlite,postgres}.goCreateOAuthSession / GetOAuthSession обновлены для работы с ip_fingerprint. PG-версия использует *string/any для NULL-семантики.
  • internal/server/handler/auth.gocomputeIPFingerprint(r) нормализует IP клиента в /24 (IPv4) или /64 (IPv6). При Login — записывается в новую сессию. При Authorize — сверяется с текущим IP. Несовпадение → slog.Warn + удаление сессии + 401.
  • internal/server/handler/auth_ipfingerprint_test.go — 3 теста: IPv4 normalization, invalid addr, fingerprintMatches edge-cases.
  • Поведение: По умолчанию fingerprint выключен (записывается, но проверка отключена). Включение — через settings.security.ip_fingerprint_enabled (default false, см. задачу ADV-XXX в roadmap). Backward compatible: пустой stored fingerprint → match always.

Security (C-CH-9 — DKIM/SPF placeholder)

  • internal/email/whitelist.go — добавлены заглушки VerifyDKIM и VerifySPF с явным TODO-комментарием и планом реализации (go-msgauth/dkim + DNS-based SPF check). Текущая реализация MatchWhitelistDual (envelope-from + Return-Path) уже работает, но без криптографической проверки подмены заголовков.
  • internal/email/whitelist_dkim_spf_test.go — тесты для заглушек, чтобы регрессия не вернула «заглушка всегда возвращает true» поведение после замены на реальную проверку.
  • Контракт зафиксирован: функции принимают (rawEmail []byte) и (senderIP, envelopeFrom, heloDomain string), возвращают (verified bool, detail string). Это позволит добавить require_dkim / require_spf флаги в email_accounts без breaking changes.

Security (C-SC-9 — authLimiter на /oauth/token)

  • internal/server/handler/oauth_ratelimit_test.go — регрессионный тест TestOAuthToken_RateLimited: доказывает, что 6-й POST /oauth/token с одного IP получает 429 + Retry-After. Подтверждает, что authLimiter (router.go:128) уже защищает весь /oauth/* route, включая /oauth/token.
  • internal/auth/oauth.go — добавлен TODO-комментарий с планом миграции с password grant на Authorization Code Flow + PKCE (RFC 6749 §1.3.3, draft-ietf-oauth-security-topics §2.4). Per-account lockout остаётся defence-in-depth.

Added (документация)

  • ADR-002 docs/adr/2026-06-07-sandbox-argv.md — argv-array без shell-string для sandbox (C-SC-2, выполнено в v0.134.19).
  • ADR-003 docs/adr/2026-06-07-oauth-refresh-rotation.md — атомарный revoke через UPDATE ... WHERE revoked_at IS NULL + client_id binding (C-SC-4, C-H-8, выполнено в v0.134.38 + миграция 072).
  • ADR-004 docs/adr/2026-06-07-audit-structured.mdresource_type + resource_id + details JSON в audit log (H-S-10, выполнено в v0.134.23).
  • ADR-005 docs/adr/2026-06-07-rag-batched-queries.md — batch Store-методы для RAG: GetFactsByIDs, SearchEntitiesByNames, Jaro-Winkler для KG same_as (H-M-1..H-M-12, выполнено в v0.134.33).
  • ADR-006 docs/adr/2026-06-07-embedder-dimensions.md — стратегия миграции при смене embedder’а с разными dim: блокирующий check + re-embed pipeline (C-M-5, частичный фикс; полный pipeline — следующий PR).
  • ADR-007 docs/adr/2026-06-07-updater-key-rotation.md — TLS hardening + Ed25519 подпись манифеста + atomic apply + key rotation plan (C-CH-7, H-CH-10..15, выполнено в v0.134.19/33).
  • ADR-008 docs/adr/2026-06-07-dual-store-embedding.md — предлагаемый struct embedding refactor DualStore (−1000 строк passthrough, L-ST-2 / L-CS-1, DEFERRED).