Next.js 16.4.1 e Node.js de outubro: o checklist de patch para apps em produção
Duas RCEs críticas no Next.js e 4 CVEs altas no Node.js em uma semana. O que muda, quem é afetado e como automatizar a checagem no CI.
- seguranca
- nextjs
- backend
Lockfiles de workflow saíram em GA e o pull_request_target será bloqueado em repos públicos em 2/12. Como auditar e travar o CI antes do prazo.

Igor Calabraro
· 8 min de leitura
Quase todo projeto Next.js ou Node que eu vejo roda testes, lint e deploy pelo GitHub Actions. E quase sempre o pipeline é tratado como detalhe: um YAML copiado de outro repositório, actions referenciadas por tag e, às vezes, um pull_request_target que ninguém lembra por que está lá. Em uma semana, o GitHub mexeu nos dois pontos. Em 28 de outubro, os lockfiles de workflow ficaram disponíveis para todos os planos. Em 2 de novembro, a empresa avisou que o pull_request_target será bloqueado por padrão em repositórios públicos a partir de 2 de dezembro. Minha tese: o CI é código de produção com acesso aos seus segredos, e agora existe prazo e ferramenta para tratá-lo assim.
| Data | O que acontece |
|---|---|
| 28/10/2026 | Lockfiles de workflow em GA (Free, Team, Enterprise Cloud) |
| 09/11/2026, 16h–18h UTC | Primeiro brownout: pull_request_target bloqueado temporariamente |
| 16/11/2026, 16h–18h UTC | Segundo brownout |
| 02/12/2026 | Bloqueio permanente por padrão em repositórios públicos |
Em horário de Brasília, os brownouts vão das 13h às 15h. Durante essas janelas, os workflows afetados não executam e aparecem como runs com falha e uma anotação de erro. Fora delas, a regra fica em modo de avaliação: os workflows rodam, mas com aviso.
Repositórios privados e internos não são afetados, nem o GitHub Enterprise Server. Se o seu repositório é público (um projeto open source, um template, um portfólio), o assunto é seu.
pull_request_target é perigosoA diferença entre os gatilhos é de contexto. O pull_request roda o código do PR com permissões reduzidas, e um PR vindo de fork não recebe os segredos do repositório. Já o pull_request_target roda no contexto do repositório base, com acesso aos segredos, como explica o próprio anúncio do GitHub. Ele foi criado para tarefas que não precisam executar o código do PR, como rotular ou comentar.
O problema começa quando alguém combina o gatilho com um checkout do código do fork:
# NÃO faça isso: código não confiável rodando com segredos
on: pull_request_target
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install && npm testUm postinstall malicioso no package.json do fork, ou um teste alterado, passa a rodar com o token de escrita e os segredos de deploy. Esse padrão ficou conhecido como "pwn request", e é exatamente o que o bloqueio quer impedir.
| Gatilho | Código que roda | Segredos em PR de fork | Uso indicado |
|---|---|---|---|
pull_request | O do PR | Não | Build, lint, testes |
pull_request_target | O do repositório base | Sim | Rotular e comentar, sem executar o PR |
workflow_run | O do repositório base | Sim | Etapa confiável depois de um build sem privilégio |
Comece listando onde o gatilho aparece:
grep -rn "pull_request_target" .github/workflows/Antes do primeiro brownout, vale também abrir a área de Insights do Actions, que o GitHub indica para ver quais runs seriam bloqueadas. Para cada workflow encontrado, há três saídas: remover o gatilho, migrar para o padrão abaixo ou, se ele for de fato necessário, liberá-lo explicitamente numa política de eventos do Actions, que permite mirar arquivos de workflow específicos.
O padrão que eu recomendo para a maioria dos casos é dividir em dois workflows. O primeiro roda no pull_request, sem segredos, e só produz um artefato:
# .github/workflows/pr-build.yml
name: PR build
on: pull_request
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm check
- run: echo "${{ github.event.pull_request.number }}" > pr-number.txt
- uses: actions/upload-artifact@v4
with:
name: pr-meta
path: pr-number.txtO segundo é disparado quando o primeiro termina. Ele roda o código do repositório base, então pode ter permissão de escrita, mas trata o artefato como entrada não confiável:
# .github/workflows/pr-comment.yml
name: Comentar resultado
on:
workflow_run:
workflows: ["PR build"]
types: [completed]
permissions:
actions: read
pull-requests: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: pr-meta
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Comentar no PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
run: |
PR=$(tr -cd '0-9' < pr-number.txt)
gh pr comment "$PR" --repo "$GITHUB_REPOSITORY" --body "Build do PR: $CONCLUSION"Dois cuidados fazem diferença aqui. O tr -cd '0-9' garante que só um número chega ao comando, mesmo que o fork tenha adulterado o arquivo. E os valores do evento passam por variáveis de ambiente, em vez de interpolação direta no run, o que evita injeção de shell.
O segundo anúncio ataca outro vetor: a tag que muda de dono. Quando você escreve uses: alguma-org/alguma-action@v3, confia que v3 vai continuar apontando para um código seguro. Se a conta do mantenedor for comprometida, a tag pode passar a apontar para outro commit, e o seu pipeline roda a versão nova sem nenhum PR.
O lockfile resolve isso mantendo a referência legível no YAML, mas executando o commit que você travou. Ele é gerado pela extensão oficial do GitHub CLI:
gh extension install github/gh-actions-lock
# na raiz do repositório: todos os workflows
gh actions-lock
# ou um workflow por vez
gh actions-lock .github/workflows/pr-build.ymlA extensão grava .github/workflows/actions.lock, com as actions e suas dependências transitivas, e reescreve os uses: para a versão exata que resolveu. Por exemplo, @v3 pode virar @v3.1.0, que aponta para o mesmo commit. Referências locais ./… passam a ser $/…, que sempre resolvem para o commit em execução.
Para o dia a dia, três pontos:
gh actions-lock --relock resolve de novo. Se o commit antigo sumiu (force-push ou exclusão), é preciso --relock --accept-moved, e isso merece investigação antes do merge.commit novo é código novo no seu CI. Um owner_id ou repo_id diferente significa que o repositório por trás daquele nome não é mais o mesmo.Administradores ainda podem exigir lockfile com a política "Require lockfile", no nível de enterprise, organização ou repositório, e checar no CI se ele está atualizado sem alterar arquivos.
O assunto não é isolado. Em 2 de novembro, a Vercel passou a bloquear versões maliciosas de pacotes npm antes e durante a instalação, em todos os planos e sem configuração. Se o lockfile do projeto apontar para uma versão marcada, o deploy falha nomeando o pacote. A direção é a mesma: a cadeia de dependências, do package.json ao YAML do CI, está virando superfície de ataque tratada com a mesma seriedade do código da aplicação.
pull_request_target antes de 9 de novembro. Em repositório público, ele para de rodar nos brownouts e de vez em 2 de dezembro.pull_request + workflow_run, e trate qualquer artefato do PR como entrada não confiável.gh actions-lock e revise o diff do lockfile como qualquer atualização de dependência.Quer esse cuidado no seu projeto?
Diagnóstico gratuito de 30 min: olho a sua stack e digo o que precisa de atenção.
Quero meu diagnósticoUm e-mail quando sair post novo. Sem spam — cancele com um clique.
Duas RCEs críticas no Next.js e 4 CVEs altas no Node.js em uma semana. O que muda, quem é afetado e como automatizar a checagem no CI.
O mesmo cuidado dos posts, aplicado ao seu produto. Diagnóstico gratuito de 30 min, sem compromisso.