ACTIONS_RUNTIME_TOKEN and scope by repo (#849)
Closes #848. Addresses [GHSA-82g9-637c-2fx2](https://github.com/go-gitea/gitea/security/advisories/GHSA-82g9-637c-2fx2) and the follow-up points raised by @ChristopherHX and @haroutp in that thread. The change is breaking only for `cache.external_server` which uses auth via a pre-shared secret. ## How auth works now 1. **Runner starts** → opens the embedded cache server on `:port`. Loads / creates a 32-byte HMAC signing key in `<cache-dir>/.secret`. 2. **Runner receives a task** → calls `handler.RegisterJob(ACTIONS_RUNTIME_TOKEN, repository)` before the job runs, defers a revoker that removes the credential on completion. Registrations are reference-counted so a stray re-register cannot revoke a live job. 3. **Job container runs `actions/cache`** → the toolkit sends `Authorization: Bearer $ACTIONS_RUNTIME_TOKEN` on every management call (`reserve`, `upload`, `commit`, `find`, `clean`). The cache server's middleware looks the token up in the registered-jobs map: miss → 401; hit → the job's repository is injected into the request context. 4. **Repository scoping** — every cache entry is stamped with `Repo` on reserve; `find`, `upload`, `commit` all verify the caller's repo matches. A job in repo A cannot see or poison a cache entry owned by repo B, even when both reach the server over the same docker bridge. GC dedup also groups by `(Repo, Key, Version)` so one repo can't age out another. 5. **Archive downloads** — `@actions/cache` does not attach Authorization when downloading `archiveLocation`, so the `find` response is a short-lived HMAC-signed URL: `…/artifacts/:id?exp=<unix>&sig=<hmac>`, 10-minute TTL, signature binds `cacheID:exp`. Tampered, expired, or foreign-secret URLs get 401. 6. **Defence-in-depth** — `ACTIONS_RUNTIME_TOKEN` is added to `task.Secrets` so the runner's log masker scrubs it from step output. ## `cache.external_server` (standalone `act_runner cache-server`) Operators set `cache.external_secret` to the same value on the runner config and the `act_runner cache-server` config. The `cache-server` then runs with bearer auth on the cache API and exposes a control-plane at `POST /_internal/{register,revoke}` (gated by the shared secret). The runner pre-registers each task's `ACTIONS_RUNTIME_TOKEN` with the remote server before the job runs and revokes it on completion. Same per-job auth + repo scoping as the embedded handler, just over the network. `cache-server` refuses to start without `cache.external_secret`; runner config load also fails when `cache.external_server` is set without `cache.external_secret`. ## User-facing changes - **One-time cache miss after upgrade.** Pre-existing entries in `bolt.db` have no `Repo` stamp and won't match any job — they'll be evicted by the normal GC. First job per cache key rebuilds its cache. - **`cache.external_server` deployments must add `cache.external_secret`.** Breaking change for anyone running a standalone `act_runner cache-server`: set the same `cache.external_secret` in both the runner config and the cache-server config. Without it neither side starts. - **No config changes required for the default setup.** Runners using the embedded cache server (the common case) keep working without any yaml edits; the auth mechanism is invisible to workflows. --- This PR was written with the help of Claude Opus 4.7 --------- Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Christopher Homberger <christopher.homberger@web.de> Reviewed-on: https://gitea.com/gitea/act_runner/pulls/849 Reviewed-by: ChristopherHX <38043+christopherhx@noreply.gitea.com>
act runner
Act runner is a runner for Gitea.
Installation
Prerequisites
Docker Engine Community version is required for docker mode. To install Docker CE, follow the official install instructions.
Download pre-built binary
Visit here and download the right version for your platform.
Build from source
make build
Build a docker image
make docker
Quickstart
Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:
[actions]
ENABLED=true
Register
./act_runner register
And you will be asked to input:
- Gitea instance URL, like
http://192.168.8.8:3000/. You should use your gitea instance ROOT_URL as the instance argument and you should not uselocalhostor127.0.0.1as instance IP; - Runner token, you can get it from
http://192.168.8.8:3000/admin/actions/runners; - Runner name, you can just leave it blank;
- Runner labels, you can just leave it blank.
The process looks like:
INFO Registering runner, arch=amd64, os=darwin, version=0.1.5.
WARN Runner in user-mode.
INFO Enter the Gitea instance URL (for example, https://gitea.com/):
http://192.168.8.8:3000/
INFO Enter the runner token:
fe884e8027dc292970d4e0303fe82b14xxxxxxxx
INFO Enter the runner name (if set empty, use hostname: Test.local):
INFO Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest):
INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04 ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04].
DEBU Successfully pinged the Gitea instance server
INFO Runner registered successfully.
You can also register with command line arguments.
./act_runner register --instance http://192.168.8.8:3000 --token <my_runner_token> --no-interactive
If the registry succeed, it will run immediately. Next time, you could run the runner directly.
Run
./act_runner daemon
Run with docker
docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/act_runner:nightly
Configuration
You can also configure the runner with a configuration file.
The configuration file is a YAML file, you can generate a sample configuration file with ./act_runner generate-config.
./act_runner generate-config > config.yaml
You can specify the configuration file path with -c/--config argument.
./act_runner -c config.yaml register # register with config file
./act_runner -c config.yaml daemon # run with config file
You can read the latest version of the configuration file online at config.example.yaml.
Example Deployments
Check out the examples directory for sample deployment types.