Join our community
Gitea is open source. Star our GitHub repo, and join our community on Discord!
We are happy to announce the release of Gitea Runner 3.0.0.
This is a runner-side release. It remains wire-compatible with existing Gitea versions; the major version bump reflects three breaking changes rather than a new server dependency.
CVE-2026-73802: Container-escape vulnerability
When privileged mode is disabled, workflow-controlled container options are now sanitized to prevent container-escape attacks. The runner strips options that can be used to break out of container isolation (#1058):
PidMode, IpcMode, UTSMode, CgroupnsMode, UsernsMode, CapAdd, SecurityOpt, Devices, DeviceCgroupRules, DeviceRequests, VolumesFrom, Runtime, CgroupParent, and Sysctls.
These options are preserved when an administrator explicitly enables privileged mode, since that already signals acceptance of expanded host access. Workflows that relied on these options on a non-privileged runner must enable privileged mode on the runner where they are genuinely required.
The headline of this release is cache service v2: standard actions/upload-artifact, actions/download-artifact, and actions/cache now work against a Gitea Runner without the gitea-upload-artifact fork. Around that, 3.0.0 adds encoded-secret masking, job hooks, and proxy propagation, along with a safety guard against two daemons sharing one runner file.
Gitea Runner now serves the GitHub Actions github.actions.results.api.v1.CacheService (v2) next to the existing v1 cache API, sharing the same store, plus the subset of the Azure blob protocol that the [@actions](https://github.com/actions)/toolkit uploader uses (#1110).
In practice this means the unmodified upstream actions work:
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/cache@v4 with: path: ~/.cache key: deps-${{ hashFiles('**/lockfile') }} - uses: actions/upload-artifact@v4 with: name: dist path: dist/actions/upload-artifact / actions/download-artifact (v4.4.0 and newer) and actions/cache no longer need the gitea-upload-artifact fork or hand-rolled workarounds. The runner patches action bundles at load time to open the GHES gate and read the cache endpoint from ACTIONS_CACHE_URL; this was validated against 118 real bundles spanning 16 actions.
Previously only the verbatim value of a secret was masked, so a secret that leaked through an action which serialized it stayed readable in the logs. Each secret and ::add-mask:: value is now also masked in its encoded forms, matching the value encoders in GitHub’s runner (#1108):
toJSON(secrets)Authorization headerEncodings that leave the value unchanged are skipped, so a plain token still costs a single replacement pair.
Operators can now run a host script inside the job environment at two lifecycle points, mirroring GitHub’s ACTIONS_RUNNER_HOOK_JOB_STARTED / ACTIONS_RUNNER_HOOK_JOB_COMPLETED (#1111):
runner: hooks: job_started: /hooks/started.sh job_completed: /hooks/completed.shjob_started runs before the job’s first step and job_completed after its last. Both scan their output for workflow commands, read back $GITHUB_ENV and $GITHUB_PATH, and fail the job on a non-zero exit code. This is useful for warming caches, provisioning credentials, or emitting telemetry around each job.
The runner now sets http_proxy, https_proxy, and no_proxy (lowercase and uppercase) so that everything it controls uses them — jobs, service containers, and Dockerfile action builds (#1112).
Sensible hosts are added to no_proxy for jobs automatically: the cache server, loopback addresses, job service containers, and the Docker daemon (so docker-in-docker keeps working). When you use a dind image, the daemon reads the same proxy variables, and the runner warns at startup if it detects a proxy mismatch.
Setting RUNNER_TOOL_CACHE now actually relocates the tool cache instead of only changing the variable, so ${{ runner.tool_cache }} and the environment variable agree (#1122). The same change lets job and service volumes displace a conflicting mount at the same target, fixes name:/target:ro being mounted read-write at the literal path /target:ro, and makes unknown keys in config.yaml produce a warning instead of silently disappearing.
1. Cache service v2 is enabled by default (#1110).
The runner now serves the v2 CacheService out of the box. If you previously deployed the gitea-upload-artifact fork or a custom artifact/cache shim, remove it and switch your workflows to the stock actions/upload-artifact@v4, actions/download-artifact@v4, and actions/cache@v4. If you must keep the previous behavior, disable it with cache.v2: false.
2. Host-escape container options are stripped without privileged mode (#1058).
Workflows that set options such as PidMode, CapAdd, SecurityOpt, Devices, or Sysctls in container.options will no longer have them applied on a non-privileged runner. If a workflow genuinely needs these, run it on a runner where the administrator has explicitly enabled privileged mode — the boundary is now enforced instead of quietly bypassed.
3. One runner process per .runner file (#1099).
The daemon and register now take a non-blocking advisory lock on a sibling <runner-file>.lock at startup. Two processes that share the same .runner file previously presented identical UUID+token credentials, so Gitea treated them as one runner and they cancelled each other’s jobs. A second process on the same host now fails fast with a clear error. Give each daemon its own .runner file. The OS releases the lock automatically on exit, even on a hard kill, so stale locks do not accumulate. Cross-host setups that share a runner file over NFS still rely on server-side detection in Gitea.
gitea/runner:2.x with gitea/runner:3.0.0 in Docker Compose, Kubernetes manifests, and service units.gitea-upload-artifact fork or artifact/cache workarounds, remove them and move to the stock upstream actions (breaking change 1).container.options host-namespace or device flags; enable privileged mode on the runner where they are genuinely required (breaking change 2)..runner file before rolling out (breaking change 3).no_proxy entries and, for dind, confirm the daemon and runner agree on proxy settings.Pre-built binaries are available from the Gitea Runner downloads page.
The release is also available from the Gitea Runner release page.
Thank you to everyone who contributed code, testing, bug reports, documentation, and feedback since the 2.0.0 release.
BREAKING
actions/upload-artifact/download-artifact (v4.4.0+) and actions/cache work without the fork (#1110)PidMode, CapAdd, SecurityOpt, Devices, Sysctls, …) when privileged mode is disabled (#1058).runner file with an advisory lock (#1099)FEATURES
BUGFIXES
RUNNER_TOOL_CACHE, allow mounting over runner paths, and warn on unknown config keys (#1122)/var/run → /run), including on Docker 29.7 (#1130, #1129)forcetypeassert lint (#1123)github.com/ulikunitz/xz to v0.5.15 (security) (#1127)FEATURES
BUGFIXES
FEATURES
BUGFIXES
FEATURES
BUGFIXES