fix: support cache-mode and make low-trust runs cache read-only (#1412)

Low-trust runs like `pull_request_target` on the default branch could poison caches that `push` runs restore. As on GitHub, they now only restore unless `cache-mode` allows saving.

This and the Gitea change can be deployed in any order.

Refs:
https://gitea.com/gitea/actionslib/pulls/33
https://github.com/go-gitea/gitea/pull/40343

_Written by Claude (Opus 5.5)._
Reviewed-on: https://gitea.com/gitea/runner/pulls/1412
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
This commit is contained in:
silverwind authored and bircni committed 2026-11-13 09:33:32 +00:00
1 parent c2046d597c
commit 4f59e3e418
8 files changed
+127 -26

No files matched your search

+19 -5
View File
@@ -543,7 +543,14 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
// is removed when the task finishes, so a leaked token stops working as
// soon as the job ends rather than remaining valid for the runner's
// lifetime.
revokeCache, resultsURL := r.registerCacheForTask(giteaRuntimeToken, cacheCredential(preset, cacheURL), reporter)
cred := cacheCredential(preset, cacheURL, cmp.Or(job.CacheMode, workflow.CacheMode))
if cacheURL != "" {
envs["ACTIONS_CACHE_MODE"] = cred.CacheMode // @actions/cache skips what the mode forbids
if cred.CacheMode != "write" {
reporter.Logf("Cache mode: %s", cred.CacheMode)
}
}
revokeCache, resultsURL := r.registerCacheForTask(giteaRuntimeToken, cred, reporter)
defer revokeCache()
eventJSON, err := json.Marshal(preset.Event)
@@ -728,17 +735,24 @@ func (r *Runner) registerCacheForTask(token string, cred artifactcache.JobCreden
}
// cacheCredential scopes the job's cache entries by ref, as GitHub does.
func cacheCredential(preset *model.GithubContext, publicURL string) artifactcache.JobCredential {
func cacheCredential(preset *model.GithubContext, publicURL, cacheMode string) artifactcache.JobCredential {
ref := preset.Ref
if strings.HasPrefix(preset.EventName, "pull_request") && preset.EventName != "pull_request_target" {
number, _ := preset.Event["number"].(float64)
if number, ok := preset.Event["number"].(float64); ok && strings.HasPrefix(preset.EventName, "pull_request") && preset.EventName != "pull_request_target" {
ref = fmt.Sprintf("refs/pull/%d/head", int64(number)) // review events take github.ref from the reviewer's commit_id
}
defaultRef := branchRef(model.AsString(model.NestedMapLookup(preset.Event, "repository", "default_branch")))
if cacheMode == "" { // only trusted events save caches for the default branch
cacheMode = "write"
if ref == defaultRef && !slices.Contains([]string{"push", "workflow_dispatch", "schedule", "delete", "registry_package"}, preset.EventName) {
cacheMode = "read"
}
}
return artifactcache.JobCredential{
Repo: preset.Repository,
Ref: ref,
BaseRef: branchRef(preset.BaseRef),
DefaultRef: branchRef(model.AsString(model.NestedMapLookup(preset.Event, "repository", "default_branch"))),
DefaultRef: defaultRef,
CacheMode: cacheMode,
PublicURL: publicURL,
}
}