Skip to content

[BUG] - Achados críticos #9

Description

@leoplantmanager

Falha de autenticação/autorização em canna-br

CRÍTICO · CVSS 3.1 ≈ 7.5 Para | Gabriel Fonseca — mantenedor, canna-brgabryelfs@gmail.com -- | -- Repositório | github.com/biliboss/canna-br Commit analisado | 8636715 · branch main · 2026-07-08 Ambiente confirmado | Produção — api.cannabr.org, verificado em 2026-08-09 Classificação | CWE-306 (Missing Authentication for Critical Function) + CWE-863 (Incorrect Authorization) Componentes | apps/api · apps/mcp (deploy self-host) · apps/agent

Resumo

Quatro superfícies do sistema não impõem uma cadeia de identidade/autorização de ponta a ponta. O achado mais grave (#1) foi confirmado ao vivo em produção com uma única requisição não-destrutiva; os demais (#2–#4) foram confirmados por leitura do código-fonte no commit acima, sem testar contra produção para evitar qualquer efeito colateral em dado real.

Achado 1 — API REST não autentica requisições de escrita CONFIRMADO EM PRODUÇÃO

resolveCallerContext (apps/api/src/auth/context.ts:34) resolveria a identidade de quem chama, mas não é invocado por nenhuma rota registrada em apps/api/src/routes/commands.ts. Campos de identidade do comando (registeredBy, grantedBy, revokedBy, validatedBy, createdBy…) são lidos diretamente do corpo JSON enviado pelo cliente.

Reprodução (não-destrutiva, já executada)
curl -sD - -X POST -H "Content-Type: application/json" -d '{}' \
  https://api.cannabr.org/v1/commands/register-member
Resultado observado
HTTP/2 400
{"error":"VALIDATION_ERROR","message":"Request body failed schema validation", ...}

Uma requisição sem qualquer credencial recebe 400 (erro de validação de schema), nunca 401/403. Isso confirma que não existe verificação de identidade antes da validação — com um corpo válido, a requisição seria aceita e processada.

Agravante: o schema completo dos comandos afetados está publicamente documentado em https://api.cannabr.org/openapi.json (HTTP 200, sem autenticação) — incluindo register-member, grant-consent, revoke-consent, create-lot, release-lot, quarantine-lot, recall-lot e validate-prescription.

Impacto: qualquer parte, sem credencial alguma, pode registrar membros, conceder/revogar consentimento LGPD, criar/liberar/recolher lotes de produto controlado, atribuindo-se qualquer identidade de operador.

Achado 2 — IDOR entre associações nas ferramentas de dispensação

apps/mcp/src/tools/request-record-dispensation.ts:91, approve-dispensation.ts:72 e draft-dispensation.ts:71 usam args.associationId — vindo do argumento da própria chamada — em vez do contexto autenticado (ctx.associationId), diferente de outras ferramentas do mesmo servidor. Confirmado até packages/app-services/src/dispensation-service.ts:20-21, onde a stream do event store é montada a partir desse valor sem cross-check.

Impacto: um operador autenticado na associação A pode solicitar/aprovar dispensação de substância controlada em nome da associação B apenas alterando um parâmetro.

Achado 3 — Deploy self-host padrão sobe o MCP sem exigir JWT

O docker-compose.yml distribuído como caminho oficial de auto-hospedagem não define JWT_SECRET para o serviço mcp; a mitigação de JWT existe em apps/mcp/src/auth.ts mas só é exigida no deploy Kamal de referência (ops/openwebui/kamal/deploy.yml:64), não no compose que uma associação rodaria ao seguir o README.

Impacto: qualquer associação que auto-hospede seguindo a documentação sobe, por padrão, um MCP que aceita headers de identidade sem verificação criptográfica.

Achado 4 — App de chat atribui papel de maior privilégio por padrão

apps/agent/app/api/mcp-client.ts:27-51 assina o token do MCP com role: process.env.CANNA_ROLE ?? "DIRETORIA" — fixo por instalação, não por sessão de usuário. A única barreira é uma senha HTTP Basic compartilhada (apps/agent/middleware.ts:17), desligada por padrão no compose (embora confirmado que está ativa no deploy real de produção — app.cannabr.org retorna 401 WWW-Authenticate: Basic realm="canna").

Impacto: em qualquer instalação que não configure manualmente o Basic Auth, todo usuário do chat atua com o papel mais privilegiado do sistema perante o MCP.

O que não foi feito

Nenhum payload válido foi enviado a register-member ou qualquer outro comando; nenhuma ferramenta MCP foi invocada; nenhuma tentativa de força bruta, scan de porta ou acesso à VPS compartilhada foi realizada. Toda evidência de produção acima é reproduzível com as duas requisições HTTP mostradas neste documento.

Sugestão de correção

  1. Reativar/gatear resolveCallerContext como preHandler obrigatório em toda rota de apps/api/src/routes/commands.ts e admin.ts, com falha explícita (não fallback) se ausente.
  2. Derivar associationId sempre de ctx, nunca do payload, nas três ferramentas MCP citadas — com teste de regressão cross-tenant.
  3. Tornar JWT_SECRET obrigatório no docker-compose.yml de self-host, com falha de inicialização se ausente.
  4. Propagar identidade/papel por sessão real do usuário do chat em vez de variável de ambiente do deployment.
  5. Considerar remover /openapi.json do acesso público (ou protegê-lo) até o item 1 estar corrigido.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions