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 observadoHTTP/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
- 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. - Derivar
associationId sempre de ctx, nunca do payload, nas três ferramentas MCP citadas — com teste de regressão cross-tenant. - Tornar
JWT_SECRET obrigatório no docker-compose.yml de self-host, com falha de inicialização se ausente. - Propagar identidade/papel por sessão real do usuário do chat em vez de variável de ambiente do deployment.
- Considerar remover
/openapi.json do acesso público (ou protegê-lo) até o item 1 estar corrigido.
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/agentResumo
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
Reprodução (não-destrutiva, já executada)resolveCallerContext(apps/api/src/auth/context.ts:34) resolveria a identidade de quem chama, mas não é invocado por nenhuma rota registrada emapps/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.Uma requisição sem qualquer credencial recebe
400(erro de validação de schema), nunca401/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) — incluindoregister-member,grant-consent,revoke-consent,create-lot,release-lot,quarantine-lot,recall-lotevalidate-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:72edraft-dispensation.ts:71usamargs.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.ymldistribuído como caminho oficial de auto-hospedagem não defineJWT_SECRETpara o serviçomcp; a mitigação de JWT existe emapps/mcp/src/auth.tsmas 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-51assina o token do MCP comrole: 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.orgretorna401 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-memberou 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
resolveCallerContextcomopreHandlerobrigatório em toda rota deapps/api/src/routes/commands.tseadmin.ts, com falha explícita (não fallback) se ausente.associationIdsempre dectx, nunca do payload, nas três ferramentas MCP citadas — com teste de regressão cross-tenant.JWT_SECRETobrigatório nodocker-compose.ymlde self-host, com falha de inicialização se ausente./openapi.jsondo acesso público (ou protegê-lo) até o item 1 estar corrigido.