For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/cicd-github-cache-poisoning.md.
A documentation index is available at /llms.txt.
Publishing workflows that build and publish runtime artifacts must not write to dependency caches. A poisoned cache can introduce malicious dependencies into packages, releases, or container images, leading to supply-chain compromise. For example, using writable caches exposed to pull request workflows.
This rule inspects GitHub Actions workflow job steps. The uses key identifies cache-aware actions such as actions/cache, actions/setup-go, actions/setup-node, actions/setup-python, Swatinem/rust-cache, and others. It also identifies with mappings containing cache-related controls.
When a job is triggered by release events or contains well-known publisher steps, cache-write flags must be disabled. For example, set lookup-only: true for actions/cache and cache: false for boolean cache controls. Steps missing a disabling flag, explicitly enabling caching, or relying on an action’s default caching behavior will be flagged. Some actions use string-valued fields to control caching, and non-configurable actions cannot be automatically fixed.
# Test case 1: Cache properly disabled with empty string in releasename:Secure Publishing Workflow - Nodeon:release:types:[published]jobs:publish-node:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-node@v4name:secure-nodewith:node-version:'18'package-manager-cache:false- run:npm ci- run:npm publishenv:NODE_AUTH_TOKEN:${{ secrets.NPM_TOKEN }}---# Test case 2: Cache disabled in Python publishing workflowname:Secure Python Publishingon:push:tags:- 'v*'jobs:publish-python:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-python@v5name:secure-pythonwith:python-version:'3.11'- uses:pypa/gh-action-pypi-publish@release/v1with:password:${{ secrets.PYPI_API_TOKEN }}---# Test case 3: actions/cache with lookup-only in publishing workflowname:Secure Release with Lookup-Only Cacheon:release:jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/cache@v4name:secure-cachewith:path:~/.npmkey:${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}lookup-only:true- uses:docker/build-push-action@v5with:push:truetags:user/app:latest---# Test case 4: Rust cache properly disabled with lookup-onlyname:Secure Rust Publishingon:release:types:[created]jobs:publish-rust:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:Swatinem/rust-cache@v2name:secure-rustwith:lookup-only:true- run:cargo publishenv:CARGO_REGISTRY_TOKEN:${{ secrets.CRATES_IO_TOKEN }}---# Test case 5: Cache enabled in non-publishing workflow (safe)name:Safe Development Buildon:pull_request:push:branches:- main- developjobs:test:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-node@v4name:safe-pr-cachewith:node-version:'18'cache:'npm'- run:npm ci- run:npm test---# Test case 6: Java without cache in releasename:Secure Java Releaseon:push:branches:- 'releases/**'jobs:publish-java:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-java@v4name:secure-javawith:distribution:'temurin'java-version:'17'- run:mvn deploy---# Test case 7: Go with cache disabled (boolean false)name:Secure Go Publishingon:release:jobs:publish-go:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-go@v5name:secure-gowith:go-version:'1.21'cache:false- uses:goreleaser/goreleaser-action@v5with:version:latestargs:release --cleanenv:GITHUB_TOKEN:${{ secrets.GITHUB_TOKEN }}---# Test case 8: DotNet without cache in Azure deploymentname:Secure DotNet Azure Deployon:push:tags:- 'v*'jobs:deploy:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-dotnet@v4name:secure-dotnetwith:dotnet-version:'8.0.x'cache:false- uses:Azure/functions-action@v1with:app-name:'my-function-app'---# Test case 9: Ruby without bundler-cachename:Secure Ruby Gem Releaseon:release:types:[published]jobs:publish-gem:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:ruby/setup-ruby@v1name:secure-rubywith:ruby-version:'3.2'bundler-cache:false- uses:rubygems/release-gem@v1---# Test case 10: Gradle with cache-disabledname:Secure Gradle Releaseon:push:tags:- 'v*'jobs:publish-gradle:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:gradle/actions/setup-gradle@v3name:secure-gradlewith:cache-disabled:true- run:./gradlew publish---# Test case 11: Docker buildx without cachename:Secure Docker Buildon:release:jobs:docker-publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:docker/setup-buildx-action@v3name:secure-buildxwith:cache-binary:false- uses:docker/build-push-action@v5with:push:truetags:myimage:latest---# Test case 12: Composer with ignore-cachename:Secure Composer Releaseon:release:jobs:publish-php:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:ramsey/composer-install@v3name:secure-composerwith:ignore-cache:'yes'- run:composer publish---# Test case 13: Non-publishing trigger with main branch (safe)name:Safe CI Buildon:push:branches:- mainjobs:build:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-python@v5name:safe-main-cachewith:python-version:'3.11'cache:'pip'- run:pip install -r requirements.txt- run:pytest---# Test case 14: UV cache disabled in releasename:Secure UV Releaseon:release:jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:astral-sh/setup-uv@v3name:secure-uvwith:enable-cache:false- run:uv publish---# Test case 15: mise-action with cache disabledname:Secure Mise Releaseon:push:tags:- 'v*'jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:jdx/mise-action@v2name:secure-misewith:cache:false- uses:softprops/action-gh-release@v1with:files:dist/*---# Test case 16: GraalVM without cachename:Secure GraalVM Releaseon:release:jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:graalvm/setup-graalvm@v1name:secure-graalvmwith:java-version:'21'distribution:'graalvm'- run:./gradlew publish---# Test case 17: Bun with no-cache enabledname:Secure Bun Releaseon:push:tags:- 'v*'jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:oven-sh/setup-bun@v2name:secure-bunwith:no-cache:true- run:bun publish---# Test case 18: Cache in non-release branch (safe)name:Safe Feature Branchon:push:branches:- 'feature/*'jobs:test:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:Mozilla-Actions/sccache-action@v0.0.5name:safe-sccache- run:cargo test---# Test case 19: actions/cache without lookup-only in non-publishing workflowname:Secure Release with Lookup-Only Cacheon:push:jobs:publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/cache@v4name:secure-cachewith:path:~/.npmkey:${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}- uses:docker/build-push-action@v5with:push:falsetags:user/app:latest
Non-Compliant Code Examples
# Test case 1: Opt-in string action with release triggername:Vulnerable Publishing Workflow - Release Triggeron:release:types:[published]jobs:publish-node:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-node@v4name:vulnerable-setup-nodewith:node-version:'18'cache:'npm'- run:npm ci- run:npm publishenv:NODE_AUTH_TOKEN:${{ secrets.NPM_TOKEN }}---# Test case 2: Opt-in action with booleanname:Vulnerable Python Publishingon:push:tags:- 'v*'jobs:publish-python:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-python@v5name:vulnerable-setup-pythonwith:python-version:'3.11'cache:'pip'- run:pip install build- uses:pypa/gh-action-pypi-publish@release/v1with:password:${{ secrets.PYPI_API_TOKEN }}---# Test case 3: actions/cache with push to release branchname:Vulnerable Release Branch Cacheon:push:branches:- 'release/*'jobs:build-and-publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/cache@v4name:vulnerable-cachewith:path:~/.npmkey:${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}- run:npm ci- uses:docker/build-push-action@v5with:push:truetags:user/app:latest---# Test case 4: Opt-out action (cache enabled by default)name:Vulnerable Rust Cacheon:release:types:[created]jobs:publish-rust:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:Swatinem/rust-cache@v2name:vulnerable-rust-cache- run:cargo publishenv:CARGO_REGISTRY_TOKEN:${{ secrets.CRATES_IO_TOKEN }}---# Test case 5: Not configurable action (always caches)name:Vulnerable sccacheon:push:tags:- 'release-*'jobs:publish-with-sccache:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:Mozilla-Actions/sccache-action@v0.0.5name:vulnerable-sccache- uses:softprops/action-gh-release@v1with:files:dist/*env:GITHUB_TOKEN:${{ secrets.GITHUB_TOKEN }}---# Test case 6: Java setup with cachename:Vulnerable Java Releaseon:push:branches:- 'releases/**'jobs:publish-java:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-java@v4name:vulnerable-setup-javawith:distribution:'temurin'java-version:'17'cache:'maven'- run:mvn deploy---# Test case 7: Multiple cache-aware actions in publisher jobname:Vulnerable Multi-Cache Jobon:release:jobs:publish-multi:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-go@v5name:vulnerable-setup-gowith:go-version:'1.21'cache:true- uses:actions/cache@v4name:vulnerable-cache-multiwith:path:~/go/pkg/modkey:${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}- uses:goreleaser/goreleaser-action@v5with:version:latestargs:release --cleanenv:GITHUB_TOKEN:${{ secrets.GITHUB_TOKEN }}---# Test case 8: Opt-in with explicit truename:Vulnerable DotNet Cacheon:push:tags:- 'v[0-9]+.*'jobs:publish-dotnet:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-dotnet@v4name:vulnerable-setup-dotnetwith:dotnet-version:'8.0.x'cache:true- uses:Azure/functions-action@v1with:app-name:'my-function-app'---# Test case 9: Ruby bundler cachename:Vulnerable Ruby Gem Releaseon:release:types:[published]jobs:publish-gem:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:ruby/setup-ruby@v1name:vulnerable-ruby-cachewith:ruby-version:'3.2'bundler-cache:true- uses:rubygems/release-gem@v1---# Test case 10: Gradle with cache-disabled set to falsename:Vulnerable Gradle Buildon:push:branches:- 'release-*'jobs:publish-gradle:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:gradle/actions/setup-gradle@v3name:vulnerable-gradle- run:./gradlew publish---# Test case 11: Docker buildx with cachename:Vulnerable Docker Buildon:release:jobs:docker-publish:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:docker/setup-buildx-action@v3name:vulnerable-buildxwith:cache-binary:true- uses:docker/build-push-action@v5with:push:truetags:myimage:latest---# Test case 12: Multiple publisher actions indicating publishingname:Vulnerable Cloud Deployon:push:branches:- mainjobs:deploy:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:actions/setup-node@v4name:vulnerable-node-cloud-deploywith:node-version:'20'cache:'npm'package-manager-cache:true- uses:google-github-actions/deploy-cloudrun@v2with:service:my-service---# Test case 13: Opt-out string actionname:Vulnerable Composer Cacheon:release:jobs:publish-php:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v4- uses:ramsey/composer-install@v3name:vulnerable-composer- run:composer publish
1
2
rulesets:- CICD / GitHub # Rules to enforce / GitHub.
Request a personalized demo
Get Started with Datadog
Ask AI
AI-generated responses may be inaccurate. Verify important info.