A GitHub Action that runs commands in a gVisor sandbox.
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.
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.
- uses: geomys/sandboxed-step@v1.1.1
with:
run: |
go get -u && go mod tidy
go test ./...The commands run in a gVisor sandbox with
- a root filesystem similar to ubuntu-24.04 (
ghcr.io/catthehacker/ubuntu:runner-24.04) - overlayed access to GITHUB_WORKSPACE (changes do not persist by default)
- GITHUB_WORKSPACE working directory
- host network access
- allow-listed environment variables
- same user as the GitHub Actions runner, with sudo access
- read-only access to tools installed by setup-* actions (via RUNNER_TOOL_CACHE)
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/checkout Action 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: true to 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 filesystem
name: 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 && go mod tidy
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 ./...