Skip to content

Modo único self-hosted: remover MANIFEST_MODE #18

Closed

Description

@patrickbrandao

Contexto

O Thinker só tem um modo, o self-hosted. Hoje common/utils/detect-self-hosted.ts decide entre cloud e self-hosted por MANIFEST_MODE (cloud/selfhosted/local) ou por detecção de container (/.dockerenv, /run/.containerenv, KUBERNETES_SERVICE_HOST), e o padrão é cloud. Fora de container (ex.: npm run start em dev), o backend roda com semântica cloud. Há cerca de 244 referências a MANIFEST_MODE, entre código, testes, compose, deploy e docs.

Depois de #14, #15, #16 e #17, os ramos cloud que sobram estão em: setup/setup.service.ts, setup/setup.controller.ts, auth/cimd-client-metadata-fetch.ts, common/utils/provider-availability.ts, common/services/request-recording-storage.service.ts, routing/tier.controller.ts, routing/proxy/provider-client.ts, routing/custom-provider/custom-provider.service.ts e database/backfills/*.boot.service.ts.

Segurança. Remover o modo não significa adotar todas as permissões do ramo self-hosted. Há dois casos com tratamento diferente:

  • CIMD (auth/cimd-client-metadata-fetch.ts:79-81). O servidor busca o documento de metadados de um cliente MCP numa URL client_id HTTPS que o próprio cliente informa durante o OAuth. Hoje allowPrivate = isSelfHosted() libera hosts privados e de loopback e tolera falha de DNS. Se a remoção do modo deixasse isso incondicional, qualquer cliente MCP poderia fazer o servidor buscar URLs na rede interna (SSRF), sem precisar de conta. Decisão: o padrão passa a ser só endereços públicos, com opt-in THINKER_CIMD_ALLOW_PRIVATE=true para quem hospeda clientes MCP na LAN.
  • Provedores customizados e modelos locais (routing/custom-provider/custom-provider.service.ts:309,412,555, validatePublicUrl(..., { allowPrivate: isSelfHosted() })). Aqui a permissão de URLs http e IPs privados é uma funcionalidade: apontar para Ollama, LM Studio ou vLLM na LAN é um caso de uso central. A URL é informada por um usuário autenticado, não por um cliente anônimo. Decisão explícita: continua permitido por padrão; os endereços de metadados de nuvem continuam sempre bloqueados (url-validation.ts). Numa instância exposta, isso só é aceitável porque o cadastro fica fechado (Fechar o cadastro público depois do primeiro admin #29, mais adiante neste milestone). O endurecimento por organização fica para a v0.3.

Escopo

  • Remover isSelfHosted() e toda a detecção de modo; manter getContainerHostAlias() (host.docker.internal/host.containers.internal).
  • Em cada arquivo listado acima, manter só o ramo self-hosted, exceto em auth/cimd-client-metadata-fetch.ts.
  • CIMD: allowPrivate passa a vir de THINKER_CIMD_ALLOW_PRIVATE (padrão false). Com false, endereços privados e de loopback são recusados e uma falha de DNS é erro (o comportamento atual do ramo cloud). Documentar a variável em packages/backend/.env.example e em docker/.env.example (comentada).
  • Provedores customizados: validatePublicUrl(..., { allowPrivate: true }) com um comentário que registre a decisão (LAN é funcionalidade; metadados de nuvem sempre bloqueados).
  • Remover o campo isSelfHosted da resposta de status do setup (o frontend já não o lê desde Remover a camada SaaS do dashboard #12).
  • Remover MANIFEST_MODE de docker/docker-compose.yml, docker/.env.example e packages/backend/.env.example. Os templates de deploy/ ficam para Templates de deploy da comunidade #30.
  • Ajuste mínimo nas migrations antigas que ainda importem isSelfHosted, mantendo o SQL do ramo self-hosted.
  • Testes: remover o MANIFEST_MODE que specs e helpers e2e definem; cobrir que, sem nenhuma variável e fora de container, o comportamento é o self-hosted.
  • Testes de SSRF:
    • unit em cimd-client-metadata-fetch: host que resolve para endereço privado ou de loopback é recusado por padrão e aceito com THINKER_CIMD_ALLOW_PRIVATE=true;
    • e2e: um fluxo OAuth MCP com client_id apontando para uma URL que resolve para endereço privado é recusado por padrão;
    • e2e de provedor customizado: URL na LAN aceita; 169.254.169.254 recusada.

Fora de escopo

Critérios de aceite

  • grep -rnE "MANIFEST_MODE|isSelfHosted|KUBERNETES_SERVICE_HOST" packages docker não encontra nada.
  • Lint, unit e e2e passam (custom-providers.url-validation, setup, request-recordings), incluindo os testes de SSRF acima.
  • Sem THINKER_CIMD_ALLOW_PRIVATE, o e2e de CIMD com client_id privado é recusado.
  • qa-docker: provedor customizado apontando para o mock em host.docker.internal funciona; gravação em filesystem funciona sem credenciais S3.

Dependências

Agente responsável

backend-dev

Activity

  1. added this to the v0.1 milestone on Oct 10, 2026
  2. patrickbrandao commented on Oct 10, 2026

    @patrickbrandao
    OwnerAuthor

    Encontrado durante a #12 (PR #51): MANIFEST_MODE ainda aparece em docker/docker-compose.yml e em docker/DOCKER_README.md.

  3. patrickbrandao commented on Oct 11, 2026

    @patrickbrandao
    OwnerAuthor

    Do PR #70 (#17): backend/src/auth/auth.instance.ts ainda lista hosts da Manifest Cloud (app.manifest.build e gateway.manifest.build) como trusted origins do Better Auth e como resources de MCP. Remover junto com o MANIFEST_MODE. O guard no-phonehome-egress fixa esse arquivo numa lista de exceções; tirar daquela lista quando limpar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:backendBackend: NestJS, TypeORM, proxy, CLI, MCP

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions