GitHub Actions em dezembro: lockfiles de workflow e o fim do pull_request_target liberado

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

Neste post

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.

#O que muda e quando

DataO que acontece
28/10/2026Lockfiles de workflow em GA (Free, Team, Enterprise Cloud)
09/11/2026, 16h–18h UTCPrimeiro brownout: pull_request_target bloqueado temporariamente
16/11/2026, 16h–18h UTCSegundo brownout
02/12/2026Bloqueio 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.

#Por que o pull_request_target é perigoso

A 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 test

Um 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.

GatilhoCódigo que rodaSegredos em PR de forkUso indicado
pull_requestO do PRNãoBuild, lint, testes
pull_request_targetO do repositório baseSimRotular e comentar, sem executar o PR
workflow_runO do repositório baseSimEtapa confiável depois de um build sem privilégio

#Passo 1: auditar o que você tem

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.

#Passo 2: separar o build sem privilégio da etapa confiável

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.txt

O 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.

#Passo 3: travar as actions com lockfile

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.yml

A 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:

  • Dependabot já conversa com o lockfile: quando ele abre um PR de versão para uma action de um workflow já travado, atualiza a entrada do lockfile no mesmo PR. Ele não faz o onboarding de workflows novos.
  • Tag que mudou de commit: 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.
  • Revise o diff do lockfile como revisa dependências: um 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 mesmo movimento fora do GitHub

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.

#Conclusão

  • Audite 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.
  • Separe build sem privilégio de etapa confiável com pull_request + workflow_run, e trate qualquer artefato do PR como entrada não confiável.
  • Trave as actions com gh actions-lock e revise o diff do lockfile como qualquer atualização de dependência.

#Fontes

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óstico

Gostou? Receba o próximo por e-mail

Um e-mail quando sair post novo. Sem spam — cancele com um clique.

Ao se inscrever, você concorda com a política de privacidade.

Continue lendo

Todos os posts

Prefere que eu cuide disso no seu projeto?

O mesmo cuidado dos posts, aplicado ao seu produto. Diagnóstico gratuito de 30 min, sem compromisso.