NOTAS DE QUEM CONSTRÓI · #055

Injeção de comando pegou a esteira de CI do próprio MCP

Um aviso de segurança de agosto de 2026 mostra o time que escreve a especificação do MCP cometendo o mesmo erro clássico de shell script que ainda derruba esteiras de CI grandes.

Welly5 MIN

Injeção de comando pegou a esteira de CI do próprio MCP

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 workflowRisco de interpolação diretaO que fazer
workflow_dispatchAlto: o input vem de quem tem acesso ao repositório, mas nem todo colaborador merece rodar comando arbitrário no runnerSempre passar por env:
issue_comment ou pull_request_targetAltíssimo: qualquer pessoa de fora consegue abrir issue ou PR e preencher o campoNunca interpolar direto, e considerar não dar secrets a esses workflows
push em branch protegidaBaixo, mas ainda existeVale 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.

Fontes

COMPARTILHARLINKEDINXWHATSAPP

// NEWSLETTER

O que a gente aprende, você recebe.

Bastidor de projeto, decisão técnica e o que estamos vendo em IA dentro das empresas. Sem enrolação e sem spam.

// PRÓXIMO PASSO

Tem um problema
parecido?