A GitHub Action that runs a command in a gVisor sandbox
Go
225
18 commits
updated Jul 28, 2026
A GitHub Action that runs commands in a gVisor sandbox.
- uses: geomys/sandboxed-step@v1.2.0
with:
run: |
go get -u -t ./...
go test ./...
Surprisingly enough, GitHub Actions with read-only permissions still receive a cache write token, allowing cache poisoning, so they are not safe to run untrusted code. This has led to supply chain compromises.
Moreover, there is no isolation between steps in a workflow, since they all run
on the same VM with root access. The only alternative is separating workflows
with workflow_run, but that has its own limitations and overhead.
This can lead to a false sense of security when running untrusted code in a workflow or step with read-only permissions (despite defaulting workflows to read-only permissions being a top-level feature).
This Action runs commands in an isolated gVisor sandbox, allowing e.g. running CI against the latest versions of dependencies without risking being affected by supply chain attacks.
The commands run in a gVisor sandbox with
ghcr.io/catthehacker/ubuntu:runner-24.04)Changes to the workspace inside the sandbox can be made to persist on the host
by setting persist-workspace-changes: 'true'. This is unlikely to be safe, as
following steps will need to treat the workspace as untrusted.
[!NOTE] This action will detect and fail if the
actions/checkoutAction has persisted authentication tokens in GITHUB_WORKSPACE (the default behavior).To use this action, you must disable credential persistence in your checkout step:
- uses: actions/checkout@v4 with: persist-credentials: falseAlternatively, you can set
allow-checkout-credentials: trueto bypass this check, but this is NOT RECOMMENDED as it will expose the GitHub token to the sandbox.
All tags use GitHub's Immutable Releases, so they can't be changed even if this repository is compromised.
run (required): Commands to run in the sandboxenv (optional): Additional environment variables to set in the sandbox (one per line, KEY=VALUE format)persist-workspace-changes (optional, default: false): Allow changes to persist on the hostdisable-network (optional, default: false): Disable network access in the sandboxallow-checkout-credentials (optional, default: false): Allow persisted checkout credentialsrootfs-image (optional): Docker image to use as the root filesystemname: Go tests
on:
push:
pull_request:
schedule: # daily at 09:42 UTC
- cron: '42 9 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go:
- { go-version: stable }
- { go-version: oldstable }
- { go-version-file: go.mod }
deps:
- locked
- latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go.go-version }}
go-version-file: ${{ matrix.go.go-version-file }}
- uses: geomys/sandboxed-step@v1.1.1
with:
run: |
if [ "${{ matrix.deps }}" = "latest" ]; then
go get -u -t ./...
fi
go test ./...
staticcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.1.1
with:
run: go run honnef.co/go/tools/cmd/staticcheck@latest ./...
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.1.1
with:
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
Go
90.0%
Shell
10.0%
A GitHub Action that runs a command in a gVisor sandbox
Go
225
18 commits
updated Jul 28, 2026
A GitHub Action that runs commands in a gVisor sandbox.
- uses: geomys/sandboxed-step@v1.2.0
with:
run: |
go get -u -t ./...
go test ./...
Surprisingly enough, GitHub Actions with read-only permissions still receive a cache write token, allowing cache poisoning, so they are not safe to run untrusted code. This has led to supply chain compromises.
Moreover, there is no isolation between steps in a workflow, since they all run
on the same VM with root access. The only alternative is separating workflows
with workflow_run, but that has its own limitations and overhead.
This can lead to a false sense of security when running untrusted code in a workflow or step with read-only permissions (despite defaulting workflows to read-only permissions being a top-level feature).
This Action runs commands in an isolated gVisor sandbox, allowing e.g. running CI against the latest versions of dependencies without risking being affected by supply chain attacks.
The commands run in a gVisor sandbox with
ghcr.io/catthehacker/ubuntu:runner-24.04)Changes to the workspace inside the sandbox can be made to persist on the host
by setting persist-workspace-changes: 'true'. This is unlikely to be safe, as
following steps will need to treat the workspace as untrusted.
[!NOTE] This action will detect and fail if the
actions/checkoutAction has persisted authentication tokens in GITHUB_WORKSPACE (the default behavior).To use this action, you must disable credential persistence in your checkout step:
- uses: actions/checkout@v4 with: persist-credentials: falseAlternatively, you can set
allow-checkout-credentials: trueto bypass this check, but this is NOT RECOMMENDED as it will expose the GitHub token to the sandbox.
All tags use GitHub's Immutable Releases, so they can't be changed even if this repository is compromised.
run (required): Commands to run in the sandboxenv (optional): Additional environment variables to set in the sandbox (one per line, KEY=VALUE format)persist-workspace-changes (optional, default: false): Allow changes to persist on the hostdisable-network (optional, default: false): Disable network access in the sandboxallow-checkout-credentials (optional, default: false): Allow persisted checkout credentialsrootfs-image (optional): Docker image to use as the root filesystemname: Go tests
on:
push:
pull_request:
schedule: # daily at 09:42 UTC
- cron: '42 9 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go:
- { go-version: stable }
- { go-version: oldstable }
- { go-version-file: go.mod }
deps:
- locked
- latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go.go-version }}
go-version-file: ${{ matrix.go.go-version-file }}
- uses: geomys/sandboxed-step@v1.1.1
with:
run: |
if [ "${{ matrix.deps }}" = "latest" ]; then
go get -u -t ./...
fi
go test ./...
staticcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.1.1
with:
run: go run honnef.co/go/tools/cmd/staticcheck@latest ./...
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-go@v6
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.1.1
with:
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
Go
90.0%
Shell
10.0%