Summary
In network-isolation (rootless) mode, actions/setup/sh/install_awf_binary.sh --rootless fails on standard GitHub-hosted runners (ubuntu-latest). The script installs into the root-owned /usr/local/{bin,lib} prefix, and the rootless preflight aborts because /usr/local/lib/awf cannot be created without sudo:
Installing awf with checksum verification (version: v0.27.10, os: Linux, arch: x86_64)
ERROR: --rootless requires write access to /usr/local/lib/awf
This directory is root-owned on standard runners. --rootless is intended
only for ARC/Kubernetes containers where the install dirs are pre-chowned
to the runner user.
##[error]Process completed with exit code 1.
The --rootless path (emitted by generateAWFInstallationStep when sandbox.agent.network-isolation: true) was written assuming ARC/Kubernetes runners where /usr/local/* is pre-chowned to the runner user. On standard hosted runners that assumption does not hold, so any network-isolation workflow that installs the published awf binary fails at the install step.
Reproduction
A workflow_dispatch workflow with:
sandbox:
agent:
network-isolation: true
version: v0.27.10
…compiles to an install step install_awf_binary.sh v0.27.10 --rootless which fails as above on ubuntu-latest.
(Observed in github/gh-aw-firewall CI; the failing run installed fine only after the fix below.)
Root cause
actions/setup/sh/install_awf_binary.sh:
AWF_INSTALL_DIR="/usr/local/bin" and AWF_LIB_DIR="/usr/local/lib/awf" are used for all modes, including --rootless.
- The rootless preflight only checks writability and errors out, rather than choosing a writable location.
- The bundle wrapper hardcodes
exec ${node_bin} /usr/local/lib/awf/awf-bundle.js, ignoring AWF_LIB_DIR.
- Nothing adds the install dir to
$GITHUB_PATH, so even a successful rootless install in a non-standard dir would not be discoverable by the later bare awf invocation (GetAWFCommandPrefix returns bare awf in network-isolation mode).
Proposed fix
Install the rootless binary into a per-user, always-writable prefix ($HOME/.local/{bin,lib/awf}) and export the bin dir to $GITHUB_PATH. Legacy (non-rootless) behavior is unchanged. This works on both standard hosted runners and ARC/K8s (no pre-chown assumption).
A validated reference implementation is on branch fix/rootless-awf-install-user-prefix (commit c2d22aa3ac):
actions/setup/sh/install_awf_binary.sh | 50 ++++++++++++++++++++---------
pkg/workflow/copilot_engine_installation.go | 3 +-
Specific changes in actions/setup/sh/install_awf_binary.sh
-
Retarget install dirs in rootless mode (after flag parsing):
if [ "$ROOTLESS" = "true" ]; then
AWF_USER_PREFIX="${HOME}/.local"
AWF_INSTALL_DIR="${AWF_USER_PREFIX}/bin"
AWF_LIB_DIR="${AWF_USER_PREFIX}/lib/awf"
fi
-
Rootless preflight: create + verify the user dirs instead of erroring on /usr/local:
if [ "$ROOTLESS" = "true" ]; then
if ! { mkdir -p "${AWF_INSTALL_DIR}" 2>/dev/null && [ -w "${AWF_INSTALL_DIR}" ]; }; then
echo "ERROR: --rootless could not create a writable install directory at ${AWF_INSTALL_DIR}" >&2
exit 1
fi
if ! { mkdir -p "${AWF_LIB_DIR}" 2>/dev/null && [ -w "${AWF_LIB_DIR}" ]; }; then
echo "ERROR: --rootless could not create a writable lib directory at ${AWF_LIB_DIR}" >&2
exit 1
fi
fi
-
Fix the bundle wrapper to use ${AWF_LIB_DIR} instead of the hardcoded path:
exec ${node_bin} ${AWF_LIB_DIR}/awf-bundle.js "\$@"
-
Expose the install dir to the current shell and subsequent steps (after install, before the --version check):
if [ "$ROOTLESS" = "true" ]; then
export PATH="${AWF_INSTALL_DIR}:${PATH}"
if [ -n "${GITHUB_PATH:-}" ]; then
echo "${AWF_INSTALL_DIR}" >> "${GITHUB_PATH}"
fi
fi
pkg/workflow/copilot_engine_installation.go
Comment-only update to reflect that --rootless now installs into $HOME/.local/{bin,lib} and exports $GITHUB_PATH (no behavior change; the step still emits install_awf_binary.sh <version> --rootless).
Validation
- Ran
install_awf_binary.sh v0.27.10 --rootless locally with a temp $HOME: installs to $HOME/.local/bin/awf + $HOME/.local/lib/awf/awf-bundle.js, awf --version → 0.27.10, $GITHUB_PATH updated, zero sudo.
go build ./... OK; TestNetworkIsolationRootless passes.
- End-to-end: a network-isolation
workflow_dispatch smoke test in github/gh-aw-firewall went green with this branch pinned (--gh-aw-ref fix/rootless-awf-install-user-prefix) — both "Install AWF binary" and "Execute GitHub Copilot CLI" succeeded, fully rootless, egress enforced.
Acceptance criteria
Summary
In network-isolation (rootless) mode,
actions/setup/sh/install_awf_binary.sh --rootlessfails on standard GitHub-hosted runners (ubuntu-latest). The script installs into the root-owned/usr/local/{bin,lib}prefix, and the rootless preflight aborts because/usr/local/lib/awfcannot be created withoutsudo:The
--rootlesspath (emitted bygenerateAWFInstallationStepwhensandbox.agent.network-isolation: true) was written assuming ARC/Kubernetes runners where/usr/local/*is pre-chowned to the runner user. On standard hosted runners that assumption does not hold, so any network-isolation workflow that installs the published awf binary fails at the install step.Reproduction
A
workflow_dispatchworkflow with:…compiles to an install step
install_awf_binary.sh v0.27.10 --rootlesswhich fails as above onubuntu-latest.(Observed in github/gh-aw-firewall CI; the failing run installed fine only after the fix below.)
Root cause
actions/setup/sh/install_awf_binary.sh:AWF_INSTALL_DIR="/usr/local/bin"andAWF_LIB_DIR="/usr/local/lib/awf"are used for all modes, including--rootless.exec ${node_bin} /usr/local/lib/awf/awf-bundle.js, ignoringAWF_LIB_DIR.$GITHUB_PATH, so even a successful rootless install in a non-standard dir would not be discoverable by the later bareawfinvocation (GetAWFCommandPrefixreturns bareawfin network-isolation mode).Proposed fix
Install the rootless binary into a per-user, always-writable prefix (
$HOME/.local/{bin,lib/awf}) and export the bin dir to$GITHUB_PATH. Legacy (non-rootless) behavior is unchanged. This works on both standard hosted runners and ARC/K8s (no pre-chown assumption).A validated reference implementation is on branch
fix/rootless-awf-install-user-prefix(commitc2d22aa3ac):Specific changes in
actions/setup/sh/install_awf_binary.shRetarget install dirs in rootless mode (after flag parsing):
Rootless preflight: create + verify the user dirs instead of erroring on
/usr/local:Fix the bundle wrapper to use
${AWF_LIB_DIR}instead of the hardcoded path:Expose the install dir to the current shell and subsequent steps (after install, before the
--versioncheck):pkg/workflow/copilot_engine_installation.goComment-only update to reflect that
--rootlessnow installs into$HOME/.local/{bin,lib}and exports$GITHUB_PATH(no behavior change; the step still emitsinstall_awf_binary.sh <version> --rootless).Validation
install_awf_binary.sh v0.27.10 --rootlesslocally with a temp$HOME: installs to$HOME/.local/bin/awf+$HOME/.local/lib/awf/awf-bundle.js,awf --version→0.27.10,$GITHUB_PATHupdated, zero sudo.go build ./...OK;TestNetworkIsolationRootlesspasses.workflow_dispatchsmoke test in github/gh-aw-firewall went green with this branch pinned (--gh-aw-ref fix/rootless-awf-install-user-prefix) — both "Install AWF binary" and "Execute GitHub Copilot CLI" succeeded, fully rootless, egress enforced.Acceptance criteria
install_awf_binary.sh --rootlesssucceeds on standardubuntu-latestrunners (no sudo, no pre-chowned/usr/local).awfis resolvable by the bareawfinvocation in later steps (via$GITHUB_PATH).