Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions actions/setup/sh/cloud_hypervisor_host_preflight.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set +o histexpand

# cloud_hypervisor_host_preflight.sh - Validate runner eligibility for AWF's
# preview cloud-hypervisor runtime.
#
# Supported scope is intentionally narrow:
# - GitHub-hosted runners only
# - Ubuntu Linux x86_64 only
# - /dev/kvm must be present

set -euo pipefail

echo "::group::cloud-hypervisor host preflight"

if [[ "${RUNNER_ENVIRONMENT:-}" != "github-hosted" ]]; then
echo "::error::cloud-hypervisor preview is supported only on GitHub-hosted runners."
exit 1
fi

if [[ "${RUNNER_OS:-}" != "Linux" ]]; then
echo "::error::cloud-hypervisor preview requires Linux runners."
exit 1
fi

if [[ "${RUNNER_ARCH:-}" != "X64" ]]; then
echo "::error::cloud-hypervisor preview requires x86_64 (RUNNER_ARCH=X64) runners."
exit 1
fi

if [[ "${ImageOS:-}" != ubuntu* ]]; then
echo "::error::cloud-hypervisor preview requires GitHub-hosted Ubuntu images (ImageOS starts with 'ubuntu')."
exit 1
fi

if ! test -e /dev/kvm; then
echo "::error::/dev/kvm is missing. cloud-hypervisor preview requires KVM-capable GitHub-hosted Ubuntu x86_64 runners."
exit 1
fi

echo "runner is eligible for cloud-hypervisor preview"
echo "::endgroup::"
158 changes: 158 additions & 0 deletions actions/setup/sh/cloud_hypervisor_setup_bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
set +o histexpand

# cloud_hypervisor_setup_bundle.sh - Download, verify, and unpack AWF's
# cloud-hypervisor guest bundle for the requested AWF version.
#
# Outputs (GITHUB_OUTPUT):
# binary_path, kernel_path, rootfs_path, supervisor_path
# binary_sha256, kernel_sha256, rootfs_sha256, supervisor_sha256

set -euo pipefail

if [[ -z "${GH_AW_AWF_VERSION:-}" ]]; then
echo "::error::GH_AW_AWF_VERSION is required"
exit 1
fi

version="${GH_AW_AWF_VERSION}"
if [[ "${version}" != v* ]]; then
version="v${version}"
fi

asset_base_url="https://github.com/github/gh-aw-firewall/releases/download/${version}"
asset_name="cloud-hypervisor-test-x86_64.tar.gz"
checksums_name="cloud-hypervisor-test-x86_64.SHA256SUMS"
manifest_name="cloud-hypervisor-test-x86_64.manifest.json"

bundle_root="${RUNNER_TEMP}/gh-aw/cloud-hypervisor/${version}"
extract_dir="${bundle_root}/bundle"
mkdir -p "${bundle_root}" "${extract_dir}"

echo "::group::Download cloud-hypervisor bundle (${version})"
curl -fsSL -o "${bundle_root}/${asset_name}" "${asset_base_url}/${asset_name}"
curl -fsSL -o "${bundle_root}/${checksums_name}" "${asset_base_url}/${checksums_name}"
curl -fsSL -o "${bundle_root}/${manifest_name}" "${asset_base_url}/${manifest_name}"
echo "downloaded release assets"
echo "::endgroup::"

echo "::group::Extract cloud-hypervisor bundle"
tar -xzf "${bundle_root}/${asset_name}" -C "${extract_dir}"
echo "bundle extracted to ${extract_dir}"
echo "::endgroup::"

sha_file="${bundle_root}/${checksums_name}"

resolve_path() {
local rel="$1"
if [[ -z "${rel}" ]]; then
return 1
fi

local cleaned="${rel#./}"
local candidate
for candidate in \
"${extract_dir}/${cleaned}" \
"${bundle_root}/${cleaned}"; do
if [[ -f "${candidate}" ]]; then
realpath "${candidate}"
return 0
fi
done

local found
found="$(find "${extract_dir}" -type f -name "$(basename "${cleaned}")" | head -n1 || true)"
if [[ -n "${found}" ]]; then
realpath "${found}"
return 0
fi

return 1
}

lookup_sha256() {
local rel="$1"
local full="$2"
local candidate
for candidate in "${rel#./}" "$(basename "${rel#./}")" "${full#${bundle_root}/}" "${full#${extract_dir}/}"; do
local sum
sum="$(awk -v target="${candidate}" '{sub(/^\.\//, "", $2); if ($2==target) {print $1; exit}}' "${sha_file}")"
if [[ -n "${sum}" ]]; then
echo "${sum}"
return 0
fi
done
return 1
}

verify_sha256() {
local expected="$1"
local file="$2"
local actual
actual="$(sha256sum "${file}" | awk '{print $1}')"
if [[ "${actual}" != "${expected}" ]]; then
echo "::error::checksum verification failed for ${file}"
exit 1
fi
}

# Artifact names are fixed by the gh-aw-firewall cloud-hypervisor release contract.
binary_rel="cloud-hypervisor"
kernel_rel="vmlinux.bin"
rootfs_rel="rootfs.ext4"
supervisor_rel="awf-supervisor"

binary_path="$(resolve_path "${binary_rel}" || true)"
kernel_path="$(resolve_path "${kernel_rel}" || true)"
rootfs_path="$(resolve_path "${rootfs_rel}" || true)"
supervisor_path="$(resolve_path "${supervisor_rel}" || true)"

if [[ -z "${binary_path}" || -z "${kernel_path}" || -z "${rootfs_path}" || -z "${supervisor_path}" ]]; then
echo "::error::failed to resolve one or more cloud-hypervisor artifact files after extraction"
exit 1
fi

binary_sha256="$(lookup_sha256 "${binary_rel}" "${binary_path}" || true)"
kernel_sha256="$(lookup_sha256 "${kernel_rel}" "${kernel_path}" || true)"
rootfs_sha256="$(lookup_sha256 "${rootfs_rel}" "${rootfs_path}" || true)"
supervisor_sha256="$(lookup_sha256 "${supervisor_rel}" "${supervisor_path}" || true)"

if [[ -z "${binary_sha256}" || -z "${kernel_sha256}" || -z "${rootfs_sha256}" || -z "${supervisor_sha256}" ]]; then
echo "::error::failed to resolve one or more cloud-hypervisor SHA256 digests from ${checksums_name}"
exit 1
fi

echo "::group::Verify cloud-hypervisor bundle checksums"
verify_sha256 "${binary_sha256}" "${binary_path}"
verify_sha256 "${kernel_sha256}" "${kernel_path}"
verify_sha256 "${rootfs_sha256}" "${rootfs_path}"
verify_sha256 "${supervisor_sha256}" "${supervisor_path}"
echo "bundle checksums verified"
echo "::endgroup::"

if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
{
echo "binary_path=${binary_path}"
echo "kernel_path=${kernel_path}"
echo "rootfs_path=${rootfs_path}"
echo "supervisor_path=${supervisor_path}"
echo "binary_sha256=${binary_sha256}"
echo "kernel_sha256=${kernel_sha256}"
echo "rootfs_sha256=${rootfs_sha256}"
echo "supervisor_sha256=${supervisor_sha256}"
} >> "${GITHUB_OUTPUT}"
fi
if [[ -n "${GITHUB_ENV:-}" ]]; then
{
echo "GH_AW_CLOUD_HYPERVISOR_BINARY=${binary_path}"
echo "GH_AW_CLOUD_HYPERVISOR_KERNEL=${kernel_path}"
echo "GH_AW_CLOUD_HYPERVISOR_ROOTFS=${rootfs_path}"
echo "GH_AW_CLOUD_HYPERVISOR_SUPERVISOR=${supervisor_path}"
echo "GH_AW_CLOUD_HYPERVISOR_BINARY_SHA256=${binary_sha256}"
echo "GH_AW_CLOUD_HYPERVISOR_KERNEL_SHA256=${kernel_sha256}"
echo "GH_AW_CLOUD_HYPERVISOR_ROOTFS_SHA256=${rootfs_sha256}"
echo "GH_AW_CLOUD_HYPERVISOR_SUPERVISOR_SHA256=${supervisor_sha256}"
} >> "${GITHUB_ENV}"
fi

echo "cloud-hypervisor bundle prepared"
4 changes: 2 additions & 2 deletions docs/public/editor/autocomplete-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,8 @@
},
"runtime": {
"type": "string",
"desc": "Container runtime for the agent container.",
"enum": ["gvisor", "docker-sbx"],
"desc": "Container runtime for the agent container. cloud-hypervisor is preview-only and limited to GitHub-hosted Ubuntu x86_64 runners with /dev/kvm.",
"enum": ["gvisor", "docker-sbx", "cloud-hypervisor"],
"leaf": true
},
"config": {
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/introduction/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ Runner topology determines where the Docker daemon and workspace live. Agent run
| ARC or another split-daemon DinD runner | Docker (default) | `runner.topology: arc-dind` stages the sysroot and workspace for the sidecar daemon. The privileged DinD sidecar creates the isolated network; the runner container remains unprivileged and does not need `NET_ADMIN`. |
| Compatible Linux runner | gVisor (`sandbox.agent.runtime: gvisor`) | The agent container runs under `runsc`, which interposes a user-space kernel between the agent and the host kernel. |
| KVM-capable Linux runner | Docker sbx (`sandbox.agent.runtime: docker-sbx`) | The agent runs inside a hardware-virtualized microVM while the firewall, API proxy, MCP Gateway, and MCP servers remain in host-side containers. |
| GitHub-hosted Ubuntu x86_64 KVM runner | Cloud Hypervisor (preview) (`sandbox.agent.runtime: cloud-hypervisor`) | The agent runs inside AWF's preview Cloud Hypervisor microVM runtime with release-asset checksum verification and digest-pinned runtime flags. |

> [!IMPORTANT]
> gVisor and Docker sbx are incompatible with `runner.topology: arc-dind`. Installing gVisor invokes `sudo` to register `runsc`, but the agent remains in the default rootless AWF mode with `sandbox.agent.sudo: false`. Docker sbx requires `sandbox.agent.sudo: true`, KVM access, and the `DOCKER_USERNAME` and `DOCKER_PAT` secrets. The compiler rejects incompatible runtime, topology, sudo, and AWF-version combinations; generated Docker sbx workflows fail fast at run time when KVM access or required secrets are unavailable.
Expand Down
40 changes: 35 additions & 5 deletions docs/src/content/docs/reference/agent-runtimes.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: Agent Runtime Selection
description: Choose and configure Docker, gVisor, Docker sbx, or ARC DinD for an agentic workflow, with runner requirements and troubleshooting guidance.
description: Choose and configure Docker, gVisor, Docker sbx, Cloud Hypervisor, or ARC DinD for an agentic workflow, with runner requirements and troubleshooting guidance.
sidebar:
order: 1340
---

Agentic workflows use AWF (Agent Workflow Firewall) to run the agent in an isolated environment. The environment can use the runner's standard Docker runtime, gVisor, or Docker sbx. ARC DinD is a runner topology that changes how the standard Docker environment is reached; it is not another value of `sandbox.agent.runtime`.
Agentic workflows use AWF (Agent Workflow Firewall) to run the agent in an isolated environment. The environment can use the runner's standard Docker runtime, gVisor, Docker sbx, or preview Cloud Hypervisor mode. ARC DinD is a runner topology that changes how the standard Docker environment is reached; it is not another value of `sandbox.agent.runtime`.

Use this page when selecting a runtime, writing workflow frontmatter, provisioning a runner, or diagnosing a runtime setup failure.

Expand All @@ -15,7 +15,7 @@ These similarly named fields control different layers:

| Field | Purpose | Values covered here |
| --- | --- | --- |
| `sandbox.agent.runtime` | Selects the isolation backend for the main agent | `gvisor`, `docker-sbx`, or omitted for Docker |
| `sandbox.agent.runtime` | Selects the isolation backend for the main agent | `gvisor`, `docker-sbx`, `cloud-hypervisor`, or omitted for Docker |
| `sandbox.agent.runtime-install` | Controls whether gh-aw installs and prepares gVisor or Docker sbx | `true` by default; `false` for a pre-provisioned runner |
| `runner.topology` | Describes how the runner reaches Docker | `arc-dind`, or omitted for a local Docker daemon |
| `tools.github.bounded-queries.runtime` | Selects the backend for bounded-query scripts only | `docker`, `gvisor`, `sbx` |
Expand All @@ -31,14 +31,16 @@ These similarly named fields control different layers:
| Docker | Linux namespaces, cgroups, and the host kernel | Linux and a usable Docker daemon | Fastest and most compatible, but the agent shares the host kernel |
| gVisor | A `runsc` user-space kernel between the agent and host kernel | Local Docker daemon, `sudo`, systemd, and access to gVisor downloads | Stronger kernel isolation with syscall compatibility and performance overhead |
| Docker sbx | A KVM-backed microVM for the agent | KVM, nested virtualization, `sudo`, apt, Docker Hub credentials, and local Docker | Strongest boundary here, but has the most setup cost and platform constraints |
| Cloud Hypervisor (preview) | A KVM-backed microVM for the agent | GitHub-hosted Ubuntu x86_64 runner with `/dev/kvm` and AWF release asset download access | Preview-only path with strict host requirements and release-asset provisioning |
| ARC DinD | Standard Docker agent container in a DinD sidecar | ARC or equivalent Kubernetes runner with a privileged DinD sidecar and shared work volume | Supports Kubernetes runner fleets, but adds split-filesystem and daemon-connectivity complexity |

Apply this selection order:

1. Use **ARC DinD** when the runner is an ARC pod or another Kubernetes runner whose Docker daemon is a DinD sidecar. Do not combine it with gVisor or Docker sbx.
2. Otherwise, use **Docker sbx** when the user requires a hardware-virtualized boundary and the runner exposes working KVM.
3. Otherwise, use **gVisor** when untrusted agent code warrants a smaller host-kernel attack surface and the workload is compatible with `runsc`.
4. Use the default **Docker** runtime when compatibility, startup time, or runner portability is more important than an additional kernel or VM boundary.
3. Use **Cloud Hypervisor (preview)** only when the runtime must be Cloud Hypervisor and the runner is GitHub-hosted Ubuntu x86_64 with `/dev/kvm`.
4. Otherwise, use **gVisor** when untrusted agent code warrants a smaller host-kernel attack surface and the workload is compatible with `runsc`.
5. Use the default **Docker** runtime when compatibility, startup time, or runner portability is more important than an additional kernel or VM boundary.

If the user's requirement is unclear, prefer Docker. Do not select a stronger runtime until the runner prerequisites are known to be available.

Expand Down Expand Up @@ -266,6 +268,34 @@ It has the highest cold-start cost, consumes more memory and disk, requires Dock

**The CLI is missing inside the microVM:** Upgrade gh-aw and recompile. Docker sbx requires engine CLIs to be staged under `${RUNNER_TEMP}/gh-aw/engine-cli`, which is visible to the microVM.

## Cloud Hypervisor (preview)

Cloud Hypervisor runs the agent in AWF's preview microVM runtime:

```aw wrap
---
on: issues
sandbox:
agent:
id: awf
runtime: cloud-hypervisor
---

Investigate this issue.
```

Preview scope is intentionally narrow:

- GitHub-hosted runners only (`RUNNER_ENVIRONMENT=github-hosted`).
- Ubuntu Linux x86_64 only (`RUNNER_OS=Linux`, `RUNNER_ARCH=X64`, `ImageOS=ubuntu*`).
- `/dev/kvm` must be present.
- `runner.topology: arc-dind` is not supported.

The compiler emits host preflight and release-asset provisioning steps before AWF runs. Provisioning downloads `cloud-hypervisor-test-x86_64.tar.gz`, `SHA256SUMS`, and `manifest.json` from the pinned `gh-aw-firewall` release, verifies checksums, and feeds AWF digest-pinned flags for the Cloud Hypervisor binary, kernel, rootfs, and supervisor.

> [!IMPORTANT]
> This runtime is preview-only. Keep expectations aligned with AWF preview support and prefer Docker sbx or gVisor when Cloud Hypervisor host constraints are not guaranteed.

## ARC with Docker-in-Docker

ARC DinD describes a split-daemon runner: the GitHub Actions runner is one container and Docker runs in a privileged sidecar. The agent still uses standard Docker, so omit `sandbox.agent.runtime`.
Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -2131,7 +2131,9 @@ sandbox:
# gVisor's runsc runtime for additional kernel-level isolation. Use 'docker-sbx'
# to run the agent inside a Docker sbx microVM with KVM hypervisor-level isolation
# — requires sandbox.agent.sudo: true, DOCKER_PAT and DOCKER_USERNAME secrets, and
# a KVM-capable runner. Incompatible with runner.topology: arc-dind.
# a KVM-capable runner. Use 'cloud-hypervisor' for AWF's preview Cloud Hypervisor
# microVM runtime (GitHub-hosted Ubuntu x86_64 + /dev/kvm only). Incompatible with
# runner.topology: arc-dind.
# (optional)
runtime: "gvisor"

Expand Down
5 changes: 5 additions & 0 deletions docs/src/content/docs/reference/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,7 @@ A `sandbox.agent` field that selects the container runtime used to execute the A

- `gvisor` — Runs the agent container under [gVisor](#gvisor-runsc) (`runsc`) for kernel-level isolation. Best for workflows processing untrusted input.
- `docker-sbx` — Runs the agent inside a [docker-sbx](#docker-sbx) KVM-isolated microVM while keeping infrastructure containers on the host.
- `cloud-hypervisor` — Runs the agent inside AWF's preview Cloud Hypervisor microVM runtime (GitHub-hosted Ubuntu x86_64 with `/dev/kvm` only).

When omitted, the default Docker runtime is used. See [Sandbox Configuration](/gh-aw/reference/sandbox/).

Expand All @@ -1493,6 +1494,10 @@ A container runtime from Google that interposes a user-space kernel between the

A KVM-hardware-virtualized microVM runtime. When `sandbox.agent.runtime: docker-sbx` is set, the AI agent runs inside a hardware-isolated microVM while infrastructure containers (MCP servers, gateway, etc.) remain on the host. Provides stronger isolation than gVisor for workloads that require full hardware-virtualization boundaries. gh-aw automatically refreshes Docker Hub OAuth credentials immediately before agent execution to prevent token expiry errors. See [Sandbox Configuration](/gh-aw/reference/sandbox/).

### cloud-hypervisor

A preview KVM-hardware-virtualized microVM runtime in AWF. When `sandbox.agent.runtime: cloud-hypervisor` is set, gh-aw emits host eligibility checks and digest-pinned release-asset provisioning for the Cloud Hypervisor binary, kernel, rootfs, and supervisor bundle. Support is intentionally limited to GitHub-hosted Ubuntu x86_64 runners with `/dev/kvm`. See [Sandbox Configuration](/gh-aw/reference/sandbox/).

### Strict Mode

Enhanced validation mode enforcing additional security checks and best practices. Enabled via `strict: true` in frontmatter or `--strict` flag when compiling.
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func registerAuditCommandFlags(cmd *cobra.Command) {
cmd.Flags().Bool("stdin", false, "Read workflow run IDs or URLs from stdin (one per line) instead of positional arguments")
cmd.Flags().String("experiment", "", "Filter to runs that include this experiment name")
cmd.Flags().String("variant", "", "Filter to runs with a specific variant value (requires --experiment)")
cmd.Flags().String("runtime", "", "Filter to runs using a specific sandbox agent runtime (e.g., gvisor, docker-sbx)")
cmd.Flags().String("runtime", "", "Filter to runs using a specific sandbox agent runtime (e.g., gvisor, docker-sbx, cloud-hypervisor)")
cmd.Flags().Bool("evals", false, "Filter to runs containing evals results (evals.jsonl); automatically downloads the usage artifact (which includes evals) when --artifacts is narrowed")
RegisterDirFlagCompletion(cmd, "output")
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/cli/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,10 @@ func TestAuditCommandInvalidRuntimeIsRejected(t *testing.T) {
require.ErrorContains(t, err, "invalid runtime value", "error message should explain the invalid value")
}

func TestValidateLogsRuntimeAllowsCloudHypervisor(t *testing.T) {
require.NoError(t, validateLogsRuntime(string(workflow.AgentRuntimeCloudHypervisor)))
}

// TestShouldSkipAuditRun_Runtime verifies that shouldSkipAuditRun's runtime
// filter matches the shared matchRuntimeFilter contract used by the logs
// orchestrator: matching runtime is not skipped, non-matching or missing
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/logs_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func validateLogsRuntime(runtime string) error {
return nil
}
logsCommandLog.Printf("Validating runtime parameter: %s", runtime)
validRuntimes := []string{string(workflow.AgentRuntimeGVisor), string(workflow.AgentRuntimeDockerSbx)}
validRuntimes := []string{string(workflow.AgentRuntimeGVisor), string(workflow.AgentRuntimeDockerSbx), string(workflow.AgentRuntimeCloudHypervisor)}
if slices.Contains(validRuntimes, runtime) {
return nil
}
Expand Down Expand Up @@ -391,7 +391,7 @@ func addLogsCommandFlags(logsCmd *cobra.Command, validArtifactSets string) {
logsCmd.Flags().String("end-date", "", "Filter runs created before this date (YYYY-MM-DD or delta like -1d, -1w, -1mo)")
addOutputFlag(logsCmd, defaultLogsOutputDir)
addEngineFilterFlag(logsCmd)
logsCmd.Flags().String("runtime", "", "Filter to runs using a specific sandbox agent runtime (e.g., gvisor, docker-sbx)")
logsCmd.Flags().String("runtime", "", "Filter to runs using a specific sandbox agent runtime (e.g., gvisor, docker-sbx, cloud-hypervisor)")
logsCmd.Flags().String("ref", "", "Filter runs by branch or tag name (e.g., main, v1.0.0)")
logsCmd.Flags().Int64("before-run-id", 0, "Filter runs with database ID before this value (exclusive)")
logsCmd.Flags().Int64("after-run-id", 0, "Filter runs with database ID after this value (exclusive)")
Expand Down
Loading