Toda automação que aceita um número digitado por alguém e cola esse número dentro de um comando de terminal carrega o mesmo risco, não importa o quão interno pareça o processo. Foi isso que aconteceu dentro do time que mantém o MCP (Model Context Protocol), o padrão que hoje faz a ponte entre agentes de IA e as ferramentas que eles chamam.
No dia 23 de agosto de 2026 o repositório oficial modelcontextprotocol/modelcontextprotocol publicou o aviso GHSA-7fx6-5m9f-89wg, severidade alta. O workflow sep-lifecycle-manual.yml, disparado manualmente pelo GitHub Actions para tocar o ciclo de vida das propostas de mudança na especificação, recebia um issue_number digitado por quem chamava o workflow e colava esse valor direto dentro de um bloco run:, sem tratar. Bastava mandar algo como $(printf INJECTED) no lugar de um número, e o comando executava dentro do runner, com acesso ao token da tarefa e a segredos do ambiente, entre eles a chave privada do app e o webhook do Discord do projeto.
O texto vira comando antes do shell ligar
O motivo técnico é chato, e é exatamente por isso que continua acontecendo. O GitHub processa a sintaxe ${{ }} como substituição de texto puro, antes mesmo de o shell começar a interpretar a linha. Se o valor que entra ali carrega $() ou crase, o shell não recebe um número: recebe um comando pronto pra rodar.
Isso não é falha do MCP como protocolo, e não afeta nenhum servidor MCP em produção. O próprio aviso é direto nesse ponto: o problema não exige cliente nem servidor MCP, é isolado da esteira de CI do repositório da especificação. Mas é justamente por isso que o caso interessa mais do que um bug qualquer. Aconteceu dentro do time que escreve o documento que milhares de outros times estão usando hoje como referência de arquitetura de agente. Se passou ali, sem ninguém notar até alguém reportar, a chance de estar dormindo num workflow qualquer do seu próprio repositório é real. E ninguém vai te avisar com um aviso de severidade alta.
O padrão que muda tudo, e cabe numa linha
O conserto não pede reescrever o workflow. Pede mover o valor de um lugar pro outro antes de ele chegar no shell.
# Vulnerável: o valor do usuário vira parte do script de shell
- run: |
echo "Processando issue ${{ github.event.inputs.issue_number }}"
gh issue comment ${{ github.event.inputs.issue_number }} --body "..."
# Corrigido: o valor entra como variável de ambiente
- env:
ISSUE_NUMBER: ${{ github.event.inputs.issue_number }}
run: |
echo "Processando issue $ISSUE_NUMBER"
gh issue comment "$ISSUE_NUMBER" --body "..."
Na primeira versão, o GitHub cola o texto do usuário direto no script antes do shell rodar: se o texto tem um comando escondido, o shell executa. Na segunda, a substituição ${{ }} ainda acontece, só que uma vez, pra preencher uma variável de ambiente do sistema operacional. O shell lê essa variável como dado através de $ISSUE_NUMBER, não como script pra interpretar. O mesmo valor malicioso que derrubava o runner na primeira versão vira só uma string estranha guardada numa variável na segunda.
Nem todo gatilho pesa igual
| Gatilho do workflow | Risco de interpolação direta | O que fazer |
|---|---|---|
| workflow_dispatch | Alto: o input vem de quem tem acesso ao repositório, mas nem todo colaborador merece rodar comando arbitrário no runner | Sempre passar por env: |
| issue_comment ou pull_request_target | Altíssimo: qualquer pessoa de fora consegue abrir issue ou PR e preencher o campo | Nunca interpolar direto, e considerar não dar secrets a esses workflows |
| push em branch protegida | Baixo, mas ainda existe | Vale o mesmo hábito, porque o custo de fazer certo é o mesmo |
O caso do MCP caiu na primeira linha da tabela: workflow_dispatch, disparado manualmente por alguém com acesso ao repositório. Não era um estranho da internet mandando payload. Era um colaborador comum preenchendo um campo que devia ser só um número. E mesmo assim bastou pra abrir caminho até token de tarefa e segredo de produção.
O que a Overflow faz
Quando a gente monta esteira de CI pra um cliente, sobretudo quando essa esteira empacota ou faz deploy de agente de IA, a busca por ${{ }} dentro de bloco run: entra no mesmo checklist da revisão de dependência e da revisão de secret exposto. É trabalho de baixo custo e fica esquecido porque não parece "trabalho de IA": parece trabalho de infraestrutura chato. E é esse trabalho chato que sustenta a parte que aparece pro cliente. Na consultoria de arquitetura que a Overflow faz antes da primeira linha de código, esse tipo de item entra no diagnóstico junto com o desenho do agente, não depois que o agente já está em produção.
Segunda de manhã
Abra a pasta .github/workflows do seu repositório e procure por ${{ dentro de blocos run:. Separe os workflows que disparam por workflow_dispatch, issue_comment ou pull_request_target: são os que aceitam input de fora do time ou de qualquer colaborador, não só de quem tem acesso de escrita direto na branch principal. Pra cada um, mova o valor pra um bloco env: antes de referenciar no shell, e troque a interpolação direta pela variável. Leva minutos por workflow e fecha uma porta que, como o próprio time do MCP mostrou há uma semana, fica aberta até em projeto que todo mundo olha de perto.

