
Encontra rotas de API com autorização mais fraca do que suas irmãs. Recuperou o CVE-2026-45316 a partir do código-fonte. Inclui os resultados negativos.
Encontra rotas de API que possuem um controle de autorização mais fraco do que suas rotas irmãs.
Ele encontrou a CVE-2026-45316 no Open WebUI apenas a partir do código-fonte, sem conhecimento de advisory:
[MISMATCH] POST /notes/{id}/pin (notes.py:pin_note_by_id)
3/3 comparable write operations on note require has_access(write);
this route requires only has_access(read)
POST /notes/{id}/update has_access(write)
POST /notes/{id}/access/update has_access(write)
DELETE /notes/{id}/delete has_access(write)
Em 2.568 rotas em cinco bases de código em produção, ele produziu 4 achados. Dois eram reais. Este README explica ambos os números.
Uma grande classe de bugs de autorização não é uma verificação quebrada. É uma verificação ausente ou enfraquecida, em uma rota cujas irmãs todas fizeram a coisa certa.
O Portainer autorizou quatro endpoints irmãos de template e não o quinto. O Signal K limitou a taxa de login HTTP, mas não o login WebSocket. A rota note-pin do Open WebUI mutava uma nota enquanto verificava permissão de leitura, quando todas as outras mutações de nota verificavam escrita.
Sempre a mesma forma:
✓ ✓ ✓ ✓ ✗
O repositório já contém a regra. Uma rota a quebrou. Então reconstrua a regra a partir do código e reporte a exceção. Sem arquivo de política, sem configuração, sem anotações. O conjunto de prova são as próprias rotas irmãs.
python3 enforcement_coverage.py /path/to/repo
python3 enforcement_coverage.py /path/to/repo --density
python3 enforcement_coverage.py /path/to/repo --json
Python 3.10+, sem dependências. Apenas FastAPI.
Os vereditos são MISSING, MISMATCH, PRESERVED, UNKNOWN. UNKNOWN se abstém
e nunca é relatado.
Extração. Resolve controles a partir da assinatura Depends(), do decorador
dependencies=[], de dependências em nível de router, de corpos de função, funções
wrapper e listas de classes de permissão.
A extração do corpo é essencial. No Open WebUI, 216 dos 608 handlers carregam a verificação decisiva dentro da função:
if user.role != 'admin' and not await AccessGrants.has_access(
user_id=user.id, resource_type='note',
resource_id=note.id, permission='write', db=db,
):
raise HTTPException(status_code=403)
Classe de operação vem do que o handler faz com o recurso, não do
verbo HTTP. POST /notes/{id}/chat é um POST que lê uma nota.
Classificação de vocabulário. Dois valores em uma mesma família de controle não formam necessariamente uma escala de força:
has_access {read, write} LEVEL
ensure_flow_permission {create,delete,execute,read,write} ACTION
has_permission {features.notes, workspace.tools} SCOPE
Apenas vocabulários LEVEL suportam comparação de força. Comparar
FlowAction.CREATE com um WRITE dominante produziu seis falsos positivos no
Langflow antes de isso ser corrigido.
Filtro de direção. Apenas desvios em direção a um controle menos restritivo são relatados. Ser mais estrito que o precedente não é uma vulnerabilidade.
Dois verdadeiros positivos.
POST /notes/{id}/pin no Open WebUI — CVE-2026-45316.
Um outro achado é uma assimetria de permissão no Netflix Dispatch:
POST /{incident_id}/resources usa IncidentViewPermission enquanto seis
operações de escrita irmãs usam IncidentEditPermission.
IncidentViewPermission retorna True para qualquer incidente não restrito;
IncidentEditPermission exige admin, commander ou reporter. O
handler enfileira a criação de tickets e grupos sem nenhuma verificação adicional.
Não foi reportado, porque não há onde reportá-lo: o repositório foi arquivado pela Netflix em 3 de setembro de 2025 e está somente leitura, não tem SECURITY.md, o reporte privado de vulnerabilidades está desabilitado para repositórios arquivados, e o Dispatch está explicitamente listado como fora do escopo no programa de recompensas da Netflix. Publicá-lo aqui é o único canal de divulgação restante. É de baixa gravidade — exige um membro autenticado da organização e afeta apenas incidentes não restritos — e o projeto não é mais mantido.
Dois falsos positivos. POST /tools/{id}/valves/user/update grava as
configurações de valve do próprio usuário e legitimamente precisa apenas de leitura na ferramenta — o
alvo da mutação é uma entidade diferente do sujeito da autorização. E uma
rota de knowledge base do Langflow onde a proteção irmã não é necessária.
Esta é a parte útil.
O Danswer tem 95,9% de cobertura de controle e produziu zero achados com 80,7%
de UNKNOWN. Seu vocabulário é require_permission('basic_access'),
('manage_connectors') — um namespace de capacidades, não uma escala de força.
Não se pode dizer que manage_connectors é mais fraco que read_connectors.
O que prevê achados é uma chamada de permissão com escopo de recurso e um
argumento de força ordenado, como has_access(resource, read|write). Apenas uma base de código em
cinco tinha isso.
Cinco padrões em cinco repositórios. Cada um precisou de trabalho no extrator antes que a análise pudesse sequer executar.
Em qualquer repositório, sob qualquer configuração. A maioria das rotas não pertence a uma família irmã de três ou mais com um controle consistente.
Nenhum destes foi antecipado de antemão.
Depends()O defeito 13 é o interessante. As rotas de recomendação de tags do Dispatch não têm o
CaseViewPermission que suas irmãs carregam. Mas essa permissão retorna True para
qualquer caso não restrito, e o serviço já verifica
visibility == restricted inline. Os caminhos são equivalentes. Detectar isso
exige análise de equivalência semântica, não estrutura.
O mecanismo funciona. Ele recuperou uma CVE publicada e uma lacuna de autorização não reportada a partir do código-fonte, com conjuntos de prova auditáveis, usando apenas informações disponíveis antes de o commit vulnerável ter sido mesclado.
O rendimento é aproximadamente um achado por 1.000 rotas, e é necessária uma arquitetura que apenas uma das cinco bases de código substanciais tinha.
Útil como ferramenta de auditoria para uma base de código com um vocabulário de permissão com escopo de recurso. Não é, com base nesta evidência, um scanner de uso geral.
A detecção estática de vulnerabilidades de controle de acesso por inferência de suposições implícitas remonta ao USENIX Security 2011. O ACMiner minerou verificações de autorização no middleware Android. O Semgrep oferece detecção com IA voltada a autorização ausente e relatou 61% de precisão em uma avaliação de cliente. A OWASP publica uma folha de dicas de Testes de Regressão de Autorização cuja abordagem recomendada é manter uma matriz Ator × Recurso × Ação manualmente.
Esta é uma abordagem estreita e determinística: recuperar apenas o que as rotas irmãs provam, abster-se caso contrário.
Licenciado sob MIT.
| repositório | rotas | controladas | achados |
|---|
| LiteLLM | 808 | 87,1% | 0 |
| Danswer / Onyx | 653 | 95,9% | 0 |
| Open WebUI | 529 | 97,2% | 2 |
| Netflix Dispatch | 291 | 36,1% | 1 |
| Langflow | 287 | 47,4% | 1 |
| repositório | padrão |
|---|
| Open WebUI | AccessGrants.has_access(resource_type=, permission=) |
| Langflow | ensure_<resource>_permission(user, Action.X) |
| LiteLLM | comparação de papel em user_api_key_dict.user_role |
| Netflix Dispatch | Depends(PermissionsDependency([CaseEditPermission])) |
| Danswer | require_permission('basic_access') |