Skip to content

Commit cfde680

Browse files
authored
Avoid caching by default (#1359)
GitHub Actions caches are shared across branches and workflows within a repository, which makes them susceptible to cache-poisoning attacks. This has been demonstrated in practice by tools such as [Cacheract](https://github.com/AdnaneKhan/Cacheract) and documented in ["The Monsters in Your Build Cache"](https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/). The trust boundary between cache writers and cache readers is not well-defined, so the safest default is to not cache at all. Users who have evaluated the trade-off for their threat model can still opt in with `cache: true` or `cache: auto`. To alleviate the performance impact this would mean for the `build-installers` flavor, we changed the git-sdk-* `ci-artifacts` workflows to provide pre-built `.tar.zst` archives. On runner images whose `tar.exe` supports Zstandard (Windows Server 2025 / `windows-latest`), the `build-installers` flavor is now served directly from these CI artifacts. Older images such as `windows-2022` fall back to the existing git-clone-and-build approach.
2 parents 6012e1d + 531896d commit cfde680

6 files changed

Lines changed: 60 additions & 19 deletions

File tree

__mocks__/@octokit/rest.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ module.exports = {
3535
}
3636
}
3737
}),
38+
getReleaseByTag: jest.fn().mockResolvedValue({
39+
status: 200,
40+
data: {
41+
html_url: 'https://github.com/git-for-windows/git-sdk-64/releases/tag/ci-artifacts',
42+
assets: [
43+
{
44+
name: 'git-sdk-x86_64-minimal.tar.gz',
45+
updated_at: '2025-08-12T12:00:00Z',
46+
browser_download_url: 'https://example.com/git-sdk-x86_64-minimal.tar.gz'
47+
},
48+
{
49+
name: 'git-sdk-x86_64-build-installers.tar.zst',
50+
updated_at: '2025-08-12T12:00:00Z',
51+
browser_download_url: 'https://example.com/git-sdk-x86_64-build-installers.tar.zst'
52+
}
53+
]
54+
}
55+
}),
3856
listReleases: jest.fn().mockResolvedValue({
3957
data: [
4058
{

action.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,14 @@ inputs:
3131
default: '250'
3232
cache:
3333
required: false
34-
description: 'Use @actions/cache to accelerate this Action'
35-
default: 'auto'
34+
description: >
35+
Use @actions/cache to accelerate this Action.
36+
Note: GitHub Actions caches are shared across branches and
37+
workflows within a repository, which makes them susceptible
38+
to cache-poisoning attacks (see e.g. Cacheract,
39+
https://github.com/AdnaneKhan/Cacheract). Caching is
40+
therefore disabled by default.
41+
default: 'false'
3642
github-token:
3743
description: >
3844
Personal access token (PAT) used to call into GitHub's REST API.

dist/index.js

Lines changed: 15 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

main.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as core from '@actions/core'
22
import {mkdirp} from './src/downloader'
33
import {restoreCache, saveCache} from '@actions/cache'
44
import process from 'process'
5+
import * as os from 'os'
56
import {spawnSync} from 'child_process'
67
import {
78
getArtifactMetadata,
@@ -45,9 +46,13 @@ async function run(): Promise<void> {
4546
const verbose = core.getInput('verbose')
4647
const msysMode = core.getInput('msys') === 'true'
4748

49+
// Windows Server 2025 / Windows 11 24H2 (build 26100+) ships a tar.exe
50+
// that handles Zstandard natively; older versions do not.
51+
const canExtractZstd = parseInt(os.release().split('.')[2]) >= 26100
52+
4853
const {artifactName, download, id} =
49-
flavor === 'minimal'
50-
? await getViaCIArtifacts(architecture, githubToken)
54+
flavor === 'minimal' || (flavor === 'build-installers' && canExtractZstd)
55+
? await getViaCIArtifacts(flavor, architecture, githubToken)
5156
: await getViaGit(flavor, architecture, githubToken)
5257
const outputDirectory =
5358
core.getInput('path') || `${getDriveLetterPrefix()}${artifactName}`

src/ci_artifacts.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ async function sleep(milliseconds: number): Promise<void> {
1111
}
1212

1313
export async function getViaCIArtifacts(
14+
flavor: string,
1415
architecture: string,
1516
githubToken?: string
1617
): Promise<{
@@ -23,7 +24,7 @@ export async function getViaCIArtifacts(
2324
}> {
2425
const owner = 'git-for-windows'
2526

26-
const {repo, artifactName} = getArtifactMetadata('minimal', architecture)
27+
const {repo, artifactName} = getArtifactMetadata(flavor, architecture)
2728

2829
const octokit = githubToken ? new Octokit({auth: githubToken}) : new Octokit()
2930

@@ -52,18 +53,22 @@ export async function getViaCIArtifacts(
5253
core.info(
5354
`Found ci-artifacts release: ${ciArtifactsResponse.data.html_url}`
5455
)
55-
const tarGzArtifact = ciArtifactsResponse.data.assets.find(asset =>
56-
asset.name.endsWith('.tar.gz')
56+
const assetName =
57+
flavor === 'build-installers'
58+
? `git-sdk-${architecture}-build-installers.tar.zst`
59+
: `git-sdk-${architecture}-minimal.tar.gz`
60+
const artifact = ciArtifactsResponse.data.assets.find(
61+
asset => asset.name === assetName
5762
)
5863

59-
if (!tarGzArtifact) {
64+
if (!artifact) {
6065
error = new Error(
61-
`Failed to find a .tar.gz artifact in the ci-artifacts release of the ${owner}/${repo} repo`
66+
`Failed to find ${assetName} in the ci-artifacts release of the ${owner}/${repo} repo`
6267
)
6368
continue
6469
}
6570

66-
return tarGzArtifact
71+
return artifact
6772
}
6873
throw error
6974
})()

0 commit comments

Comments
 (0)