Skipbox

Are self-hosted runners safe for private repositories? GitHub's hardening rules, done for you

A self-hosted runner is safe for a private repository only once you add what GitHub's hosted runners give you and a plain self-hosted runner lacks: a clean machine, a runner that takes one job, and separate machines for code you don't trust. GitHub's security reference says self-hosted runners "do not have guarantees around running in ephemeral clean virtual machines, and can be persistently compromised by untrusted code in a workflow".

Skipbox is a self-hosted runner with that work done. Your jobs run on a machine that is your account's alone, its credentials are wiped after every job, each runner is registered for one job, and code from people without write access runs in a fresh VM. Turn on Clean every job and every job gets a fresh VM. A flat $39 a month, unlimited minutes; change one runs-on line.

What GitHub warns about

All from GitHub's security reference and its self-hosted runner docs:

  • The machine remembers. A self-hosted runner doesn't need a clean instance for every job. What one job leaves (files, processes, containers, tokens) is there for the next.
  • Private repositories aren't exempt. "Anyone who can fork the repository and open a pull request (generally those with read access to the repository) are able to compromise the self-hosted runner environment, including gaining access to secrets and the GITHUB_TOKEN."
  • Public repositories. Self-hosted runners "should almost never be used for public repositories", because any user can open a pull request.
  • One runner, many repositories. An organization-level runner takes jobs from every repository allowed to use it, so one compromise spreads. Runner groups limit which repositories reach which runners.
  • What sits on the machine. SSH keys, API tokens, and network access to internal services or a cloud's metadata service are all within a job's reach.
  • Deleting the machine after a job isn't enough by itself. "There is no way to guarantee that a self-hosted runner only runs one job." GitHub's fix is a just-in-time runner, which performs at most one job, started in a clean environment by your own automation.
  • Approval settings don't protect your hardware. Once a fork's workflow is allowed to run, "potentially malicious user-controlled workflow code will execute automatically" on a self-hosted runner.

What a secure setup takes

GitHub's guidance, as work:

Built by hand, that is an autoscaler, an image pipeline and a second runner pool. On Skipbox it is the default.

What Skipbox isolates, exactly

Your own machine

Each plan, and the trial, gives your account its own VM. It runs only your account's jobs, one at a time, and it is kept between jobs: Docker images and build cache, package caches and installed tools stay, which is why the next job is fast.

After every job, Skipbox:

  • deletes the runner's credentials
  • stops every process of the runner user and removes every container
  • deletes credential files: ~/.docker/config.json, ~/.npmrc, ~/.git-credentials, ~/.config/gh, ~/.aws/credentials, ~/.kube/config, ~/.ssh/id_*
  • removes the git credentials in the checkout and in the home directories' git config, the checkout's hooks and its untracked files
  • checks the machine (disk, packages, Docker, the runner, DNS, leftover processes and containers) and rebuilds it from the image if the check fails

From 13 November 2026, your checkout and its build output (Cargo's target/) stay on your own machine for your next jobs. The machine is also rebuilt when you press Reset machine, and when the image is updated, at least once a month.

Jobs of your own account share that disk, as on a runner you host. To keep repositories or branches apart from each other, turn on Clean every job.

A fresh VM

These jobs run in a new VM that has run no other job, deleted with its disk when the job ends:

  • every job, when Clean every job is on
  • code from people without write access, such as pull requests from forks
  • jobs that arrive while all your own machines are busy

A run counts as your team's when its code is the repository's own and the person who started it has write, maintain or admin access. Everything else is treated as outside code: a fork's pull request, a pull_request_target or workflow_run started from a fork's code, an issue_comment by someone without write access. The rule is the same on private and public repositories. An outside job reads your caches and can't change them, and one that reaches your own machine anyway fails before its steps run; the machine is then reset.

Around the VM

  • One job per runner. Each runner is registered just in time for a single job. A runner left registered after its job is removed.
  • No inbound connections. Nothing on the internet can connect to a job's VM.
  • No access to your code. The Skipbox app can't read your repositories; each job checks out your code itself, inside its VM.
  • No other customer. No other organization's job runs on your machine.

A job's VM holds the workflow's secrets and GitHub's token for that job, as on any runner. Give a job only the secrets it needs. How jobs are kept apart has every detail.

What it costs next to GitHub's runners

Self-hosted minutes are free on GitHub, and Skipbox's runners are self-hosted runners.

GitHub's figures are the bill on GitHub Team after its included compute. Price your own minutes in the calculator on the home page; no sign-in.

Moving is one line

jobs:
  build:
    runs-on: skipbox # was ubuntu-latest, or self-hosted
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test
  1. Sign in to Skipbox with GitHub.
  2. Install the Skipbox GitHub app and pick your repositories.
  3. Change runs-on and push.

For a fresh VM on every job, turn on Clean every job in Plans and billing.

Try it on a private repository

The trial takes no card and runs for 7 days or 3,000 job-minutes. Move one job to runs-on: skipbox, and retire the runner you were about to harden.

FAQ

Are self-hosted runners safe for private repositories?

Only with work GitHub's hosted runners do for you. GitHub's docs say a self-hosted runner has no guarantee of a clean, ephemeral machine and can be persistently compromised by untrusted code, and that on a private repository anyone who can fork it and open a pull request can reach the runner, its secrets and its GITHUB_TOKEN. Skipbox closes those gaps for you: a machine that runs only your account's jobs, credentials wiped after every job, a runner registered for one job, and a fresh VM for code from people without write access.

How do I set up a GitHub Actions self-hosted runner securely, with one fresh VM per job?

Register each runner just in time for a single job, start it in a VM that has run no other job, delete the VM when the job ends, keep outsiders' pull requests off machines your team uses, and forward the runner's logs before the VM goes. That is an autoscaler to build and operate. On Skipbox it is a switch: turn on Clean every job and every job runs in a new VM, deleted with its disk afterwards. Change runs-on to skipbox to start.

Does Skipbox run every job on a fresh VM?

With Clean every job on, yes: every job runs in a new VM that has run no other job, deleted with its disk when the job ends. Without it, your team's jobs run on your account's own machine, kept between jobs so Docker layers and package caches stay; after each job Skipbox deletes the runner's credentials, stops its processes, removes every container and deletes credential files. Code from people without write access, and jobs that arrive while your machines are busy, always get a fresh VM.

What does a self-hosted runner keep between jobs?

Everything, unless you wipe it: files, processes, containers, Docker credentials, SSH keys and tokens the last job wrote. GitHub's docs say a self-hosted runner doesn't need a clean instance for every job. On a Skipbox own machine, the caches and your checkout stay and the credentials, processes and containers go after every job; a machine that fails its check after a job is rebuilt from a clean image.

Can a pull request from a fork run on my self-hosted runner?

On a runner you host, yes, once the repository's fork policy lets its workflows run: GitHub's docs warn that user-controlled workflow code then executes on your infrastructure. On Skipbox a fork's pull request never runs on your own machine. It runs in a fresh VM that can read your caches but can't change them, and a job from a fork that reaches your own machine anyway fails before its steps run.

Are self-hosted runners safe for public repositories?

GitHub says self-hosted runners should almost never be used for public repositories, because any user can open a pull request and compromise the machine. Skipbox runs public repositories' jobs by keeping those pull requests off your machine: code from people without write access runs in a fresh VM, deleted afterwards, and your own machine runs only your team's code.

Updated November 14, 2026

Faster CI/CD with an always-warm cache

Your warm runner at a flat price

1/12 the cost of GitHub at full use. Flat price, so run it all day. 7× GitHub's cache space.

  • No card
  • Sign in with GitHub
  • Cancel anytime