You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On ARC/DinD deployments, no code in the workflow or agent container can run as root. Anything requiring root permissions must be done before the action runs — i.e., at image build time.
This means system-level build essentials (gcc, make, linker, dev libraries) cannot be installed by workflow steps or inside the agent. They must be baked into a pre-built image during the release pipeline, where root is available.
This issue proposes a versioned build-tools sysroot image that provides system-level build infrastructure. Language SDKs (Go, Node, Java, .NET, Rust, Python) remain on-demand via setup-* actions, installed to a shared tool cache volume.
Critically, this must be easy for gh-aw to configure. A single runner.topology: arc-dind selector (from #5591) should be all that's needed to activate the entire ARC/DinD mode — including sysroot image usage, tool cache redirection, and split-filesystem handling — rather than requiring callers to coordinate 6+ scattered config keys.
An explicit value in any key always wins over the topology default.
What gh-aw emits
The gh-aw compiler just emits:
{ "runner": { "topology": "arc-dind" } }
AWF resolves all the split-FS, sysroot, tool cache, and network isolation details internally. This is the single stable contract between gh-aw and the firewall for ARC deployments.
Architecture
build-tools image provides: setup-* actions provide (on demand):
───────────────────────────── ──────────────────────────────────
bash, capsh, coreutils Go 1.22 (setup-go)
gcc, g++, make, ld Node 20 (setup-node)
libssl-dev, libc6-dev, libicu-dev Java 21 (setup-java)
pkg-config, cmake, autoconf .NET 8.0 (setup-dotnet)
git, curl, wget Rust (setup-rust)
system linker, dynamic loader Python 3.12 (setup-python)
specific versions per workflow
build-tools = static system base (same for all workflows, requires root → baked at image build time). Tool cache = dynamic, per-workflow (setup-* actions choose versions, no root needed → installed at workflow runtime).
End-to-End Walkthrough
Phase 1: Image Build Time (root available)
The build-tools image is built once in the release pipeline with docker build (runs as root):
go build ./... # go from tool cache, gcc/ld/libc from sysroot
npm install # node from tool cache, node-gyp uses gcc from sysroot
javac Main.java # javac from tool cache
make # make from sysroot
1. New Dockerfile: containers/build-tools/Dockerfile
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
# System-level build essentials only — no language SDKs.# Language SDKs are installed on-demand via setup-* actions to a shared tool cache.# Everything here requires root to install → done at image build time.## Source of truth for parity: actions/runner-images toolset-2204.json .apt sectionRUN apt-get update && apt-get install -y --no-install-recommends \
# Compilers and linker
build-essential g++ gcc make \
# Build system tools
autoconf automake libtool pkg-config cmake \
# Development libraries (needed for native module compilation: cgo, node-gyp, etc.)
libssl-dev libicu-dev libc6-dev libsqlite3-dev libyaml-dev \
libcurl4-openssl-dev libgsl-dev \
# Binary tools
binutils bison flex m4 \
# CLI utilities
git curl wget jq zip unzip tar bzip2 \
# System essentials for chroot
ca-certificates bash coreutils findutils file \
# Capability management (for AWF privilege drop)
libcap2-bin \
&& rm -rf /var/lib/apt/lists/*
2. Release workflow addition
Add a build-build-tools job to .github/workflows/release.yml, parallel to existing image builds. Same pattern as existing images: build, push, sign with cosign, generate SBOM, attest.
Tags: ghcr.io/${{ github.repository }}/build-tools:${version} and :latest.
3. runner.topology: arc-dind config integration
When runner.topology === "arc-dind", AWF automatically:
Behavior
Default applied
Overridable?
Network isolation (no iptables)
true
Yes
DinD pre-stage dirs
true
Yes
Sysroot image
build-tools:{current-version}
Yes (container.sysrootImage)
Sysroot-stage init container in compose
Emitted
—
buildSystemMounts() replaced with sysroot volume
Yes
—
Tool cache warning if under /opt
Emitted
—
Split-FS path-prefix probes
Activated
—
The gh-aw compiler emits just:
{ "runner": { "topology": "arc-dind" } }
4. Schema changes
Add runner.topology enum (standard | arc-dind) to docs/awf-config.schema.json
Add container.sysrootImage string to schema
Regenerate src/awf-config-schema.json via scripts/generate-schema.mjs
Add runner?: { topology? } to AwfFileConfig in src/config-file.ts
In mapAwfFileConfigToCliOptions, apply overridable arc-dind defaults
5. Tool cache integration
The existing workflow logic already handles tool cache mounting:
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"if [ -d"$GH_AW_TOOL_CACHE" ];thenif [[ "$GH_AW_TOOL_CACHE"!= /opt/* ]];then
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"fifi
On ARC with RUNNER_TOOL_CACHE=/tmp/gh-aw/tool-cache, this activates automatically. If topology is arc-dind and tool cache is under /opt, AWF emits an actionable warning explaining the misconfiguration.
6. Parity maintenance: drift-detection CI
A scheduled job comparing the build-tools Dockerfile against upstream actions/runner-imagestoolset-2204.json apt section.
7. Versioning
All images share the same version tag from the release:
Image
Purpose
agent:0.28.0
Security infra, entrypoint, capsh, iptables
build-tools:0.28.0
System base: gcc, make, dev libraries (no language SDKs)
agent-act:0.28.0
Agent on catthehacker base (existing)
squid:0.28.0
Proxy (existing)
api-proxy:0.28.0
API credential proxy (existing)
cli-proxy:0.28.0
CLI proxy (existing)
Security Considerations
The sysroot image is signed with cosign and has an SBOM attestation, same as all other AWF images.
The sysroot volume is mounted read-only into the agent — the agent (post-privilege-drop) cannot modify it.
Tool cache from setup-* actions is also mounted read-only at agent exec time.
Provenance is AWF-controlled (built from our Dockerfile, from our release workflow) — not sourced from runner-writable or daemon-writable paths for pre-capsh execution.
Summary
On ARC/DinD deployments, no code in the workflow or agent container can run as root. Anything requiring root permissions must be done before the action runs — i.e., at image build time.
This means system-level build essentials (gcc, make, linker, dev libraries) cannot be installed by workflow steps or inside the agent. They must be baked into a pre-built image during the release pipeline, where root is available.
This issue proposes a versioned
build-toolssysroot image that provides system-level build infrastructure. Language SDKs (Go, Node, Java, .NET, Rust, Python) remain on-demand viasetup-*actions, installed to a shared tool cache volume.Critically, this must be easy for gh-aw to configure. A single
runner.topology: arc-dindselector (from #5591) should be all that's needed to activate the entire ARC/DinD mode — including sysroot image usage, tool cache redirection, and split-filesystem handling — rather than requiring callers to coordinate 6+ scattered config keys.Related
/hostbase userland not staged on split-fs runnersrunner.topologyconfig selector for ARC/DinD modeDesign Principle: Privilege Hierarchy
apt-get install, system library installation, compiler setupsetup-*actions (user-writable tool cache), checkout, config generationNothing in the workflow or agent ever needs root. The
build-toolsimage front-loads all privileged work into the release process.Configuration:
runner.topologyIntegrationThe problem today
Getting AWF to behave correctly on ARC/DinD requires the caller to independently set ~6 scattered keys:
network.isolationcontainer.dockerHostcontainer.dockerHostPathPrefixcontainer.runnerToolCachePath/optonto shared volumecontainer.sysrootImagechroot.binariesSourcePath/chroot.identitydind.preStageDirs/dind.workDirThe solution: one selector activates everything
{ "runner": { "topology": "arc-dind" // "standard" (default) | "arc-dind" } }When
runner.topology === "arc-dind", AWF applies overridable defaults:network.isolation→true(ARC k8s lacks NET_ADMIN)dind.preStageDirs→truecontainer.sysrootImage→ghcr.io/github/gh-aw-firewall/build-tools:{version}(NEW)RUNNER_TOOL_CACHEis unset or under/optAn explicit value in any key always wins over the topology default.
What gh-aw emits
The gh-aw compiler just emits:
{ "runner": { "topology": "arc-dind" } }AWF resolves all the split-FS, sysroot, tool cache, and network isolation details internally. This is the single stable contract between gh-aw and the firewall for ARC deployments.
Architecture
build-tools = static system base (same for all workflows, requires root → baked at image build time).
Tool cache = dynamic, per-workflow (setup-* actions choose versions, no root needed → installed at workflow runtime).
End-to-End Walkthrough
Phase 1: Image Build Time (root available)
The
build-toolsimage is built once in the release pipeline withdocker build(runs as root):This produces an image layer containing:
Published as
ghcr.io/github/gh-aw-firewall/build-tools:0.28.0, signed with cosign.Phase 2: Workflow Runtime (no root)
Step 1: Tool cache redirection (early in workflow)
Now
setup-*actions will install to/tmp/gh-aw/tool-cache/— a path on the shared emptyDir between runner and DinD containers.Step 2: setup-* actions run on the runner (no root needed)
These download pre-built tarballs and extract them — no root required. The tool cache directory tree after setup:
Step 3: Capture env vars
Step 4: AWF starts the compose stack (from the runner)
AWF receives the config with
runner.topology: arc-dindand generates docker-compose.yml:Phase 3: Inside the Agent Container (no root)
Step 5: entrypoint.sh runs
/host/bin/shexists (from sysroot) ✅capshat/host/usr/sbin/capsh✅chroot /hostsucceedsStep 6: Agent wrapper sets up PATH
The bash wrapper command dynamically discovers SDK bin directories:
After this, PATH contains:
Step 7: Agent (Copilot CLI) executes
The agent can now run build commands:
What Comes From Where (Summary Table)
sysroot:/host:roapt-get installat image build time (root)/tmp/gh-aw/tool-cache:/host/.../tool-cache:rosetup-*actions at workflow runtime (no root)/workspace:/host/workspace:rwnode_modules/, build artifactsactions/checkout+ agent writes/tmp/gh-aw:/host/tmp/gh-aw:rw/host/tmp/awf-runner-bin/copilot:roinstall_copilot_cli.sh+ existing staging mechanismComparison: Non-ARC vs ARC
build-toolssysroot imagesetup-*→ host tool cache → visible via/host/optsetup-*→ shared tool cache → mounted into agent/host/hostmountsawf-runner-binmechanismrunner.topology: arc-dind→ AWF handles the restProposed Implementation
1. New Dockerfile:
containers/build-tools/Dockerfile2. Release workflow addition
Add a
build-build-toolsjob to.github/workflows/release.yml, parallel to existing image builds. Same pattern as existing images: build, push, sign with cosign, generate SBOM, attest.Tags:
ghcr.io/${{ github.repository }}/build-tools:${version}and:latest.3.
runner.topology: arc-dindconfig integrationWhen
runner.topology === "arc-dind", AWF automatically:truetruebuild-tools:{current-version}container.sysrootImage)buildSystemMounts()replaced with sysroot volume/optThe gh-aw compiler emits just:
{ "runner": { "topology": "arc-dind" } }4. Schema changes
runner.topologyenum (standard|arc-dind) todocs/awf-config.schema.jsoncontainer.sysrootImagestring to schemasrc/awf-config-schema.jsonviascripts/generate-schema.mjsrunner?: { topology? }toAwfFileConfiginsrc/config-file.tsmapAwfFileConfigToCliOptions, apply overridable arc-dind defaults5. Tool cache integration
The existing workflow logic already handles tool cache mounting:
On ARC with
RUNNER_TOOL_CACHE=/tmp/gh-aw/tool-cache, this activates automatically. If topology isarc-dindand tool cache is under/opt, AWF emits an actionable warning explaining the misconfiguration.6. Parity maintenance: drift-detection CI
A scheduled job comparing the build-tools Dockerfile against upstream
actions/runner-imagestoolset-2204.jsonapt section.7. Versioning
All images share the same version tag from the release:
agent:0.28.0build-tools:0.28.0agent-act:0.28.0squid:0.28.0api-proxy:0.28.0cli-proxy:0.28.0Security Considerations
capshprivilege-drop originates from the AWF-signed image, never from runner/daemon-writable paths.Acceptance Criteria
containers/build-tools/Dockerfileexists with system-level build essentials (no language SDKs)ghcr.io/.../build-tools:{version}runner.topologyenum (standard|arc-dind) added to AWF config schemacontainer.sysrootImagefield added to schemarunner.topology: arc-dindapplies overridable defaults (network isolation, sysroot image, pre-stage dirs)arc-dindarc-dindand cache is under/optRUNNER_TOOL_CACHE=/tmp/gh-aw/tool-cache) works withsetup-*actionsgcc --version(from sysroot) andgo version(from tool cache) inside chroottoolset-2204.jsonapt packagesdocs/arc-dind.mdupdated