Disable automatic caching for sensitive events (#992)

## Summary

- disable `enable-cache: auto` for `pull_request_target`,
`workflow_run`, and `release` events
- disable automatic caching for tag pushes while leaving branch pushes
unchanged
- preserve explicit `enable-cache: true` as an override
- run a `workflow_run` integration fixture with `act` in pull request CI
and verify caching is disabled
- document the behavior and update the published bundles

## Testing

- `npm run all`
- `actionlint .github/workflows/test.yml
__tests__/workflows/workflow-run.yml`
- `uvx zizmor __tests__/workflows/workflow-run.yml`

Closes #984

Refs: pi-session 019fec42-9b26-714e-a359-830ac4401ecd
This commit is contained in:
Kevin Stillhammer
2026-08-10 18:12:08 +02:00
committed by GitHub
parent b68407c192
commit f45168497b
9 changed files with 181 additions and 6 deletions
+20
View File
@@ -1087,6 +1087,25 @@ jobs:
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
test-workflow-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install act
run: gh extension install https://github.com/nektos/gh-act
env:
GH_TOKEN: ${{ github.token }}
- name: Verify workflow_run disables automatic caching with act
run: |
gh act workflow_run \
-W __tests__/workflows/workflow-run.yml \
-P ubuntu-latest=catthehacker/ubuntu:act-latest \
--env RUNNER_ENVIRONMENT=github-hosted
env:
GH_TOKEN: ${{ github.token }}
validate-typings: validate-typings:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
@@ -1143,6 +1162,7 @@ jobs:
- test-restore-python-installs - test-restore-python-installs
- test-python-install-dir - test-python-install-dir
- test-act - test-act
- test-workflow-run
- validate-typings - validate-typings
if: always() if: always()
steps: steps:
+1 -1
View File
@@ -74,7 +74,7 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed
# Used when downloading uv from GitHub releases # Used when downloading uv from GitHub releases
github-token: ${{ github.token }} github-token: ${{ github.token }}
# Enable uploading of the uv cache: true, false, or auto (enabled on GitHub-hosted runners, disabled on self-hosted runners) # Enable the GitHub Actions cache for uv: true, false, or auto (enabled on GitHub-hosted runners except for release, tag push, pull_request_target, and workflow_run events; disabled on self-hosted runners)
enable-cache: "auto" enable-cache: "auto"
# Glob pattern to match files relative to the repository root to control the cache # Glob pattern to match files relative to the repository root to control the cache
+64
View File
@@ -12,6 +12,8 @@ import {
let mockInputs: Record<string, string> = {}; let mockInputs: Record<string, string> = {};
const tempDirs: string[] = []; const tempDirs: string[] = [];
const ORIGINAL_GITHUB_EVENT_NAME = process.env.GITHUB_EVENT_NAME;
const ORIGINAL_GITHUB_REF = process.env.GITHUB_REF;
const ORIGINAL_HOME = process.env.HOME; const ORIGINAL_HOME = process.env.HOME;
const ORIGINAL_RUNNER_ENVIRONMENT = process.env.RUNNER_ENVIRONMENT; const ORIGINAL_RUNNER_ENVIRONMENT = process.env.RUNNER_ENVIRONMENT;
const ORIGINAL_RUNNER_TEMP = process.env.RUNNER_TEMP; const ORIGINAL_RUNNER_TEMP = process.env.RUNNER_TEMP;
@@ -52,6 +54,8 @@ function createTempProject(files: Record<string, string> = {}): string {
function resetEnvironment(): void { function resetEnvironment(): void {
jest.clearAllMocks(); jest.clearAllMocks();
mockInputs = {}; mockInputs = {};
delete process.env.GITHUB_EVENT_NAME;
delete process.env.GITHUB_REF;
process.env.HOME = "/home/testuser"; process.env.HOME = "/home/testuser";
delete process.env.RUNNER_ENVIRONMENT; delete process.env.RUNNER_ENVIRONMENT;
delete process.env.RUNNER_TEMP; delete process.env.RUNNER_TEMP;
@@ -64,6 +68,8 @@ function restoreEnvironment(): void {
fs.rmSync(dir, { force: true, recursive: true }); fs.rmSync(dir, { force: true, recursive: true });
} }
process.env.GITHUB_EVENT_NAME = ORIGINAL_GITHUB_EVENT_NAME;
process.env.GITHUB_REF = ORIGINAL_GITHUB_REF;
process.env.HOME = ORIGINAL_HOME; process.env.HOME = ORIGINAL_HOME;
process.env.RUNNER_ENVIRONMENT = ORIGINAL_RUNNER_ENVIRONMENT; process.env.RUNNER_ENVIRONMENT = ORIGINAL_RUNNER_ENVIRONMENT;
process.env.RUNNER_TEMP = ORIGINAL_RUNNER_TEMP; process.env.RUNNER_TEMP = ORIGINAL_RUNNER_TEMP;
@@ -94,6 +100,64 @@ describe("loadInputs", () => {
expect(inputs.resolutionStrategy).toBe("highest"); expect(inputs.resolutionStrategy).toBe("highest");
}); });
it.each([
"pull_request_target",
"workflow_run",
"release",
])("disables automatic caching for the %s event", (eventName) => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = eventName;
const inputs = loadInputs();
expect(inputs.enableCache).toBe(false);
expect(mockInfo).toHaveBeenCalledWith(
`Caching is disabled for the ${eventName} event`,
);
});
it("disables automatic caching for tag pushes", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "push";
process.env.GITHUB_REF = "refs/tags/v1.0.0";
const inputs = loadInputs();
expect(inputs.enableCache).toBe(false);
expect(mockInfo).toHaveBeenCalledWith("Caching is disabled for tag pushes");
});
it("enables automatic caching for branch pushes", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "auto";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "push";
process.env.GITHUB_REF = "refs/heads/main";
const inputs = loadInputs();
expect(inputs.enableCache).toBe(true);
});
it("honors explicitly enabled caching for sensitive events", () => {
mockInputs["working-directory"] = "/workspace";
mockInputs["enable-cache"] = "true";
process.env.RUNNER_ENVIRONMENT = "github-hosted";
process.env.RUNNER_TEMP = "/runner-temp";
process.env.GITHUB_EVENT_NAME = "release";
const inputs = loadInputs();
expect(inputs.enableCache).toBe(true);
});
it("uses cache-dir from pyproject.toml when present", () => { it("uses cache-dir from pyproject.toml when present", () => {
mockInputs["working-directory"] = createTempProject({ mockInputs["working-directory"] = createTempProject({
"pyproject.toml": `[project] "pyproject.toml": `[project]
+42
View File
@@ -0,0 +1,42 @@
name: "test workflow_run caching"
on: # zizmor: ignore[dangerous-triggers] this workflow is a test fixture executed by act only
workflow_run:
workflows:
- test
types:
- completed
permissions:
contents: read
jobs:
test-cache-disabled:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup uv with automatic caching
id: setup-uv
uses: ./
- name: Verify automatic caching is disabled
env:
CACHE_KEY: ${{ steps.setup-uv.outputs.cache-key }}
run: |
if [ "$GITHUB_EVENT_NAME" != "workflow_run" ]; then
echo "Expected workflow_run event, got: $GITHUB_EVENT_NAME"
exit 1
fi
if [ "$RUNNER_ENVIRONMENT" != "github-hosted" ]; then
echo "Expected a simulated GitHub-hosted runner, got: $RUNNER_ENVIRONMENT"
exit 1
fi
if [ -n "$CACHE_KEY" ]; then
echo "Cache key should not be set for a workflow_run event: $CACHE_KEY"
exit 1
fi
if [ -n "$UV_CACHE_DIR" ]; then
echo "UV_CACHE_DIR should not be set for a workflow_run event: $UV_CACHE_DIR"
exit 1
fi
+1 -1
View File
@@ -33,7 +33,7 @@ inputs:
required: false required: false
default: ${{ github.token }} default: ${{ github.token }}
enable-cache: enable-cache:
description: "Enable uploading of the uv cache" description: "Enable the GitHub Actions cache for uv. 'auto' enables caching on GitHub-hosted runners except for release, tag push, pull_request_target, and workflow_run events."
default: "auto" default: "auto"
cache-dependency-glob: cache-dependency-glob:
description: description:
Generated Vendored
+14 -1
View File
@@ -62624,7 +62624,20 @@ function getVenvPath(workingDirectory, activateEnvironment) {
function getEnableCache() { function getEnableCache() {
const enableCacheInput = getInput("enable-cache"); const enableCacheInput = getInput("enable-cache");
if (enableCacheInput === "auto") { if (enableCacheInput === "auto") {
return process.env.RUNNER_ENVIRONMENT === "github-hosted"; if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
return false;
}
const eventName = process.env.GITHUB_EVENT_NAME;
const isTagPush = eventName === "push" && process.env.GITHUB_REF?.startsWith("refs/tags/");
if (isTagPush) {
info2("Caching is disabled for tag pushes");
return false;
}
if (eventName === "pull_request_target" || eventName === "workflow_run" || eventName === "release") {
info2(`Caching is disabled for the ${eventName} event`);
return false;
}
return true;
} }
return enableCacheInput === "true"; return enableCacheInput === "true";
} }
Generated Vendored
+14 -1
View File
@@ -98255,7 +98255,20 @@ function getVenvPath(workingDirectory, activateEnvironment2) {
function getEnableCache() { function getEnableCache() {
const enableCacheInput = getInput("enable-cache"); const enableCacheInput = getInput("enable-cache");
if (enableCacheInput === "auto") { if (enableCacheInput === "auto") {
return process.env.RUNNER_ENVIRONMENT === "github-hosted"; if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
return false;
}
const eventName = process.env.GITHUB_EVENT_NAME;
const isTagPush = eventName === "push" && process.env.GITHUB_REF?.startsWith("refs/tags/");
if (isTagPush) {
info2("Caching is disabled for tag pushes");
return false;
}
if (eventName === "pull_request_target" || eventName === "workflow_run" || eventName === "release") {
info2(`Caching is disabled for the ${eventName} event`);
return false;
}
return true;
} }
return enableCacheInput === "true"; return enableCacheInput === "true";
} }
+4 -1
View File
@@ -38,7 +38,10 @@ The computed cache key is available as the `cache-key` output:
If you enable caching, the [uv cache](https://docs.astral.sh/uv/concepts/cache/) will be uploaded to If you enable caching, the [uv cache](https://docs.astral.sh/uv/concepts/cache/) will be uploaded to
the GitHub Actions cache. This can speed up runs that reuse the cache by several minutes. the GitHub Actions cache. This can speed up runs that reuse the cache by several minutes.
Caching is enabled by default on GitHub-hosted runners. With the default `enable-cache: auto`, caching is enabled on GitHub-hosted runners except for
`release`, tag push, `pull_request_target`, and `workflow_run` events. Caching is disabled for these
events to prevent insecure or release-sensitive jobs from restoring potentially poisoned caches.
Set `enable-cache: true` to explicitly enable caching for any event.
> [!TIP] > [!TIP]
> >
+21 -1
View File
@@ -140,7 +140,27 @@ function getVenvPath(
function getEnableCache(): boolean { function getEnableCache(): boolean {
const enableCacheInput = core.getInput("enable-cache"); const enableCacheInput = core.getInput("enable-cache");
if (enableCacheInput === "auto") { if (enableCacheInput === "auto") {
return process.env.RUNNER_ENVIRONMENT === "github-hosted"; if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
return false;
}
const eventName = process.env.GITHUB_EVENT_NAME;
const isTagPush =
eventName === "push" && process.env.GITHUB_REF?.startsWith("refs/tags/");
if (isTagPush) {
log.info("Caching is disabled for tag pushes");
return false;
}
if (
eventName === "pull_request_target" ||
eventName === "workflow_run" ||
eventName === "release"
) {
log.info(`Caching is disabled for the ${eventName} event`);
return false;
}
return true;
} }
return enableCacheInput === "true"; return enableCacheInput === "true";
} }