Skip to content

Runner security model

A runner executes code you wrote (or someone opened a pull request with). Treat it as a place where arbitrary code runs, and design the trust boundaries around that.

Boundary Protection
Runner to control plane Outbound HTTPS only. The runner authenticates with a per-runner credential it obtained using the pool registration token. There is no inbound path into your network
Control plane to job A job is a signed, leased assignment. It carries only what that job needs
Job to your cloud Short-lived OIDC token ($SLIPWAY_ID_TOKEN, 15 minutes) used for workload identity federation. No long-lived cloud keys on the runner
Job to other jobs Isolated per executor (below). Workspaces are not shared
Tenants Hosted runners are never shared across orgs. Self-hosted pools are yours alone
  • A fresh workspace and environment built from pipeline, job and step env.
  • Secrets referenced in env, with and services.env, delivered for that job only and masked in log output on the runner before lines are shipped.
  • If permissions.id_token: write, an OIDC token whose claims identify org, project, pipeline, run, job, ref, sha, environment, actor and runner pool. permissions: also narrows what the job token can do against the Slipway API (contents, artifacts, secrets, deployments).
  • Access to its cache and artifacts through the runner, not directly to storage.

Secrets are never placed on the command line of the runner process, never written to the log, and are not available to jobs that did not reference them. Environment-scoped secrets are only delivered to jobs bound to that environment, after protection rules pass.

Executor What is isolated What is not
kubernetes Pod per job, optional namespace, service account and node selection per pool; deleted at the end Shared node kernel (use sandboxed runtimes or dedicated nodes for untrusted code)
docker Container per job on a user-defined network; allow_privileged is off by default; allowed_volumes restricts bind mounts Anything the container can reach through a mounted Docker socket (do not mount it into jobs)
shell Workspace directory Everything else on the host. A job can read other jobs’ files and the runner’s own credentials. Use only on dedicated, disposable machines

Do not run untrusted code on a runner that can reach production or hold secrets. Use a separate pool with a label such as untrusted, no secrets, an isolated network and ephemeral pods, and keep deploy jobs on a different pool.

  • Run the runner as an unprivileged user (the packages do; the image runs as a non-root user).
  • Use the kubernetes executor for shared or multi-team CI; avoid privileged containers and the Docker socket.
  • Give each pool the minimum network egress it needs. Block cloud metadata endpoints from job pods unless a job requires them.
  • Keep registration tokens out of images and shell history: use --token-file or a secret manager.
  • Use workload identity (OIDC) instead of stored cloud credentials. See OIDC job tokens.
  • Scope trust in your cloud: match on environment, ref and runner_pool claims, not just the org.
  • Keep the runner updated. Enable self_update or roll the image.
  • Use ephemeral runners (a new pod per job, or auto-scaled VMs) for anything that handles untrusted input.
  • Watch the audit log for new runners, pool token creation and changes to runner pool settings.

Self-hosted runners can hold a failed job (debug_hold, default 15 minutes) so an authorised user can open a shell inside it. Restrict who has that permission, since it is access to the job’s environment and secrets.