Repository navigation
fix(integrations): state do OAuth assinado e token só para o endpoint do provedor (CRM-29) - #82
Merged
Conversation
…o no servidor (CRM-29) O state das integrações OAuth dos agentes era um JSON em base64 sem assinatura, e o complete_authorization confiava nos campos dele, inclusive no endereço do servidor MCP usado na troca do código, na descoberta de metadados e gravado na integração. src/services/oauth_state.py emite o state com agent_id, emissão, nonce e um HMAC-SHA256 com chave derivada do ENCRYPTION_KEY, e o valida em tempo constante, com validade de 15 minutos e o agent_id esperado. O serviço base, o PayPal e as duas integrações Google passam a emitir e validar por ele, e o endereço do MCP é sempre o do próprio serviço. O agent_id segue no topo do JSON, que é onde a tela de retorno do frontend o lê. As falhas do complete_authorization respondem uma mensagem genérica e o detalhe fica no log do servidor, de onde também saem a resposta da troca de token, prévias e prefixos de access e refresh token. Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
… erros (CRM-29)
As 12 integrações OAuth tinham, além do callback autenticado que o
frontend usa (POST /agents/{agent_id}/integrations/{provider}/callback),
um GET /integrations/{provider}/callback que tirava o agente e o
endereço do MCP do próprio state. Nenhum cliente chama essas rotas; elas
saem, com o registro no main.py.
Nas rotas de início e de callback, as falhas respondem mensagens
genéricas em vez do texto da exceção. O error_response recebia um
`request` que não existia no handler de início (NameError em 10
provedores) e o corpo da requisição no de callback; as chamadas deixam
de passá-lo.
Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
… state traz (CRM-29) test_oauth_state cobre o state emitido: valida para o próprio agente, o frontend segue lendo o agent_id com um decode simples, não leva URL, e é recusado se vier no formato sem assinatura, alterado em qualquer campo, de outro agente, vencido, do futuro, assinado com outra chave ou sem chave configurada. test_oauth_callback_contract roda os 12 provedores com o cliente HTTP substituído por um que grava as chamadas: state sem assinatura ou de outro agente é recusado antes de qualquer requisição; a URL de autorização leva o state assinado; a falha da troca de token não volta na resposta; e mesmo um state assinado com mcp_url não escolhe o endereço usado nem o gravado (base e PayPal). O teste de RBAC deixa de exigir os callbacks sem permissão e passa a garantir que eles não voltem e que o callback seja o POST com integrations:connect. O oauth_state.py recebe a formatação do black. Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
…niciaram o fluxo (CRM-29) Achado da review: o state assinado conferia o agente e a validade, mas não quem completava o fluxo. Dentro da validade, um state emitido para um usuário era aceito na sessão de outro, inclusive de outra conta; nas integrações Google, sem PKCE, nada mais segurava isso. A assinatura passa a cobrir também o usuário e a conta de quem chama (src/api/oauth_caller.py), sem levá-los no state: o callback recalcula a assinatura com o chamador do request, e outro usuário ou outra conta não batem. Sem identidade de usuário, nada é emitido nem aceito. As 12 dependências que montam os serviços das integrações registram o chamador no serviço (binds_oauth_caller). Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
… MCP do provedor (CRM-29) Achado da review: com o callback corrigido, o runtime ainda mandava o Bearer da integração ao mcp_url gravado no config do agente, e esse valor pode ser escrito fora do callback (o PUT de configuração mescla o que o corpo traz, e configs gravados antes da correção podem ter qualquer coisa). src/services/mcp_provider_endpoints.py lista os endpoints de cada integração nativa, com todas as grafias que o código já usa, para que uma conexão que funciona hoje não mude de URL. Um mcp_url fora da lista é trocado pelo padrão do provedor nos configs do runtime das 10 integrações MCP, no carregamento de tools do agente e na descoberta de tools. Servidores MCP customizados seguem com a URL configurada. O builder do agente deixa de logar o config inteiro da integração (com tokens) e passa a resolver a URL por integration_mcp_url_for. Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
Um mcp_url gravado de fora da lista nunca recebe o Bearer nos configs de runtime que leem a integração (asana, canva, monday, paypal, supabase, atlassian, hubspot), nem nos que recebem a URL de quem chama (github, notion, linear), nem no carregamento de tools do agente, nem na descoberta de tools. Os padrões espalhados (serviços, configs do runtime e builder) são conferidos contra a lista, para que um novo padrão não corte uma conexão em silêncio. Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
Co-Authored-By: Claude Code <[EMAIL_REDACTED]>
|
Sorry @nickoliveira23, your pull request is larger than the review limit of 150,000 diff characters |
daniloleonecarneiro
approved these changes
Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
O parâmetro
statedo OAuth das integrações dos agentes passa a ser emitido e conferido pelo servidor, e o endpoint MCP do fluxo passa a ser resolvido só no servidor:src/services/oauth_state.py): HMAC-SHA256 com chave derivada daENCRYPTION_KEY, validade de 15 minutos e nonce. Ele carrega sóagent_id,issued_at,nonceesig. A assinatura também cobre quem iniciou o fluxo (usuário e, quando existe, o contexto da requisição), sem que esses ids viajem no state: um fluxo iniciado por um usuário só pode ser concluído por ele.GET /<provedor>/callbacksaíram; o frontend conclui o fluxo peloPOST .../callback, autenticado e comintegrations:connect.src/services/mcp_provider_endpoints.py). Servidores MCP customizados mantêm a URL configurada.Também entram:
Test plan
pytest tests/unit --ignore=tests/unit/test_exception_handlers.py→ 698 passedtest_oauth_state.pytest_oauth_callback_contract.py: os 12 provedores, incluindo Google e PayPaltest_oauth_caller.pytest_mcp_provider_endpoints.pytest_integrations_rbac_gates.py: atualizadoGET .../callback→ 404mcp_urlfora da tabela gravado na configuração → o runtime usa o endpoint do provedorChangelog
fix(integrations): state do OAuth assinado, com validade e amarrado a quem iniciou o fluxofix(integrations): callback só pelo POST autenticado; erros genéricos; sem token em logfix(integrations): o token de integração nativa só vai ao endpoint MCP do provedor