Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 8 additions & 0 deletions .github/aw/enclaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ enclaves:
- A fresh masked capability is generated per workflow run and passed only to the MCP gateway and AWF, never to the primary agent environment.
- `timeout:` per enclave entry is capped at 4,740 seconds (AWF reserves the final 60 seconds of its 4,800-second finite-disclosure bucket for cleanup). The gateway itself enforces a 4,860-second tool timeout (4,800s AWF bucket + 60s transport allowance) — treat this as an enforcement bound, not a wall-clock guarantee.

## Experimental Cloud Hypervisor runtime

`enclaves[].runtime: cloud-hypervisor` is an experimental preview for static script and agent enclaves on GitHub-hosted Ubuntu x86_64 KVM runners. Set `sandbox.agent.version: v0.28.47` or newer; the older default AWF version does not support this executor. Configuration and behavior may change without deprecation. Unsupported hosts, artifacts, or configurations fail closed without fallback to Docker or another runtime.

Set the entry-level `runtime: cloud-hypervisor` on every enclave entry; omitted `runtime` and `docker` retain Docker execution and cannot be mixed with VM entries. The compiler enables the AWF preview and stages release-attested enclave artifacts without selecting a VM runtime for the primary agent or changing its mounts or TTY.

Do not combine this preview with `dynamic`, enclave `image` overrides, Docker host path prefixes, `runner.topology: arc-dind`, or primary `sandbox.agent.runtime` values `sbx`, `nvx`, or `cloud-hypervisor`. Agent entries also require `agent.model`, the API proxy with a configured route for their fixed engine/profile, and no Docker-in-Docker. Only the default `agent.engine: copilot` is implemented; the emitted `COPILOT_GITHUB_TOKEN` or `COPILOT_PROVIDER_API_KEY` configures its route, not a target hostname or pricing overlay alone. Static `agent.github` and `agent.tools.github` access is unsupported in the VM preview until a scoped executor bearer handoff is available. Existing disclosure limits and MCP tools remain unchanged.

## Agent GitHub tool configuration

Prefer this configuration shape for new workflows:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/smoke-claude-auto.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/smoke-claude-copilot-auto.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/smoke-codex-bare-auto.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/smoke-pi-auto.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions actions/setup/sh/cloud_hypervisor_setup_enclave_artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
set +o histexpand
set -euo pipefail
umask 077

version="${GH_AW_AWF_VERSION:?GH_AW_AWF_VERSION is required}"
[[ "${version}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]] || {
echo "::error::invalid AWF release tag: ${version}"
exit 1
}

setup_dir="${RUNNER_TEMP}/gh-aw/cloud-hypervisor-enclave-setup"
mkdir -p "${setup_dir}"
manifest_name=cloud-hypervisor-enclave-rootfs-x86_64.manifest.json
bundle_name=cloud-hypervisor-enclave-rootfs-x86_64.manifest.sigstore.jsonl
gh release download "${version}" \
--repo github/gh-aw-firewall \
--dir "${setup_dir}" \
--clobber \
--pattern setup-cloud-hypervisor-enclave-artifacts.sh \
--pattern "${manifest_name}" \
--pattern "${bundle_name}"

# The installer is not separately attested. Bind its bytes to the source commit
# in the release-attested enclave manifest before executing any downloaded code.
gh attestation verify "${setup_dir}/${manifest_name}" \
--repo github/gh-aw-firewall \
--bundle "${setup_dir}/${bundle_name}" \
--signer-workflow github/gh-aw-firewall/.github/workflows/release.yml \
--deny-self-hosted-runners
source_commit="$(jq -er --arg tag "${version}" \
'.release | select(.repository == "github/gh-aw-firewall" and .tag == $tag) | .sourceCommit' \
"${setup_dir}/${manifest_name}")"
[[ "${source_commit}" =~ ^[0-9a-f]{40}$ ]] || {
echo "::error::invalid enclave artifact source commit"
exit 1
}
curl -fsSL \
"https://raw.githubusercontent.com/github/gh-aw-firewall/${source_commit}/guest/cloud-hypervisor/setup-enclave-artifacts.sh" \
-o "${setup_dir}/source-installer.sh"
cmp "${setup_dir}/source-installer.sh" "${setup_dir}/setup-cloud-hypervisor-enclave-artifacts.sh"

# The release installer verifies the enclave manifest and both rootfs attestations
# and exports the host-only artifact paths through GITHUB_ENV.
bash "${setup_dir}/setup-cloud-hypervisor-enclave-artifacts.sh" \
"${version}" "${RUNNER_TEMP}/gh-aw/cloud-hypervisor-enclaves"
27 changes: 27 additions & 0 deletions docs/src/content/docs/experimental/enclaves.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@ The generated gateway upstream uses a fresh masked capability for each workflow

This compiler contract depends on the unified enclave implementation from `github/gh-aw-firewall#6992`. Until that change is available in an AWF release, pinning an older AWF version will not provide the enclave server.

## Cloud Hypervisor enclave runtime

> [!WARNING]
> `enclaves[].runtime: cloud-hypervisor` is an experimental preview. Configuration and behavior may change without deprecation. Unsupported hosts, artifacts, and configurations fail closed; execution never falls back to Docker or another runtime.

Set `runtime: cloud-hypervisor` on a static script or agent entry to select AWF's trusted host enclave microVM executor. Omitting `runtime`, or setting it to `docker`, retains Docker execution. Cloud Hypervisor enclaves require AWF `v0.28.47` or newer, GitHub-hosted Ubuntu x86_64 KVM runners, and release-attested enclave artifacts.

```aw wrap
on: workflow_dispatch
runs-on: ubuntu-24.04
engine: copilot
sandbox:
agent:
runtime: docker
version: v0.28.47
enclaves:
- script:
runtime: cloud-hypervisor
repos:
- repo: octo-org/private-service
sensitivity: confidential
```

The compiler enables the AWF preview and stages its runtime artifacts without changing the primary agent's runtime, mounts, or TTY. All enclave entries must select Cloud Hypervisor together: mixed Docker/default and VM entries, `dynamic`, and enclave `image` overrides are unsupported. The primary agent cannot use `sbx`, `nvx`, or `cloud-hypervisor`; `runner.topology: arc-dind` and Docker host path prefixes are also unsupported.

Agent enclaves additionally require `agent.model`, the API proxy with a configured provider route matching their fixed engine/profile, and no Docker-in-Docker. Only the default `agent.engine: copilot` is currently implemented; its route requires the emitted `COPILOT_GITHUB_TOKEN` or `COPILOT_PROVIDER_API_KEY` credential, not just a target hostname or pricing overlay. Static `agent.github` and `agent.tools.github` access is unavailable in this VM preview because the scoped executor bearer handoff is not yet supported. Repository disclosure limits and the existing MCP tool contract remain unchanged.

## GitHub tool access from agent enclaves

Prefer `agent.tools.github` for new workflows:
Expand Down
4 changes: 4 additions & 0 deletions pkg/constants/version_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ const AWFArcDindMinVersion Version = "v0.27.20"
// cloud-hypervisor preview runtime and its release assets.
const AWFCloudHypervisorMinVersion Version = "v0.28.11"

// AWFEnclaveCloudHypervisorMinVersion is the minimum AWF version supporting
// the release-attested Cloud Hypervisor enclave executor preview.
const AWFEnclaveCloudHypervisorMinVersion Version = "v0.28.47"

// AWFLegacySecurityMinVersion is the minimum AWF version that supports the
// --legacy-security flag and unconditional API proxy (gh-aw-firewall#6207).
// Workflows pinning an older AWF version must use the old --security-mode compat behavior.
Expand Down
36 changes: 36 additions & 0 deletions pkg/parser/enclaves_runtime_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package parser

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestEnclavesRuntimeSchema(t *testing.T) {
for _, executor := range []string{"script", "agent"} {
for _, runtime := range []string{"", "docker", "cloud-hypervisor", "gvisor", "sbx", "nvx", "unknown"} {
t.Run(executor+"/"+runtime, func(t *testing.T) {
entry := map[string]any{
executor: map[string]any{},
"repos": []any{
map[string]any{"repo": "octo-org/private-service", "sensitivity": "confidential"},
},
}
if executor == "agent" {
entry[executor] = map[string]any{"model": "gpt-5"}
}
if runtime != "" {
entry["runtime"] = runtime
}
err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(map[string]any{
"on": "workflow_dispatch", "engine": "copilot", "enclaves": []any{entry},
}, "workflow.md")
if runtime == "" || runtime == "docker" || runtime == "cloud-hypervisor" {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
}
14 changes: 12 additions & 2 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3674,7 +3674,12 @@
}
},
"repos": { "$ref": "#/$defs/enclave-repos" },
"runtime": { "type": "string", "enum": ["docker"], "default": "docker" },
"runtime": {
"type": "string",
"description": "⚠️ Experimental. 'cloud-hypervisor' selects the preview enclave microVM executor on GitHub-hosted Ubuntu x86_64 KVM runners. Unsupported configurations fail closed without fallback. Configuration and behavior may change without deprecation. Omit this field for Docker.",
"enum": ["docker", "cloud-hypervisor"],
"default": "docker"
},
"image": { "type": "string", "minLength": 1, "maxLength": 500 },
"timeout": { "type": "integer", "minimum": 1, "maximum": 4740, "default": 30 },
"memory-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "512m" },
Expand Down Expand Up @@ -3756,7 +3761,12 @@
},
"repos": { "$ref": "#/$defs/enclave-repos" },
"dynamic": { "$ref": "#/$defs/enclave-dynamic-policy" },
"runtime": { "type": "string", "enum": ["docker"], "default": "docker" },
"runtime": {
"type": "string",
"description": "⚠️ Experimental. 'cloud-hypervisor' selects the preview enclave microVM executor on GitHub-hosted Ubuntu x86_64 KVM runners. Unsupported configurations fail closed without fallback. Configuration and behavior may change without deprecation. Omit this field for Docker.",
"enum": ["docker", "cloud-hypervisor"],
"default": "docker"
},
"image": { "type": "string", "minLength": 1, "maxLength": 500 },
"timeout": { "type": "integer", "minimum": 1, "maximum": 4740, "default": 120 },
"memory-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "512m" },
Expand Down
16 changes: 16 additions & 0 deletions pkg/workflow/awf_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,19 @@ func buildAWFConfigPrintfArg(awfConfigJSON string, hasMaxAICreditsExport bool) s
if strings.Contains(awfConfigJSON, awfArcDindRootPathExpr) {
preservedVars = append(preservedVars, "RUNNER_TEMP")
}
for _, name := range []string{
"GH_AW_CLOUD_HYPERVISOR_BINARY",
"GH_AW_CLOUD_HYPERVISOR_KERNEL",
"GH_AW_CLOUD_HYPERVISOR_ROOTFS",
"GH_AW_CLOUD_HYPERVISOR_SUPERVISOR",
"GH_AW_CLOUD_HYPERVISOR_ARTIFACT_MANIFEST",
"GH_AW_CLOUD_HYPERVISOR_ARTIFACT_MANIFEST_BUNDLE",
"GH_AW_CLOUD_HYPERVISOR_ARTIFACT_RELEASE_TAG",
} {
if strings.Contains(awfConfigJSON, "${"+name+"}") {
preservedVars = append(preservedVars, name)
}
}
if len(preservedVars) > 0 {
return shellEscapeArgWithVarsPreserved(awfConfigJSON, preservedVars...)
}
Expand Down Expand Up @@ -680,6 +693,9 @@ func GetAWFCommandPrefix(workflowData *WorkflowData) string {
awfHelpersLog.Printf("Using custom AWF command: %s", agentConfig.Command)
return agentConfig.Command
}
if hasCloudHypervisorEnclaves(workflowData) {
return constants.AWFCloudHypervisorCommand
}

// The runtime profile decides whether AWF runs rootless or with host privileges.
profile := resolveSandboxRuntimeProfile(agentConfig)
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ type AWFAPITargetConfig struct {
// AWFContainerConfig is the "container" section of the AWF config file.
// It maps to container execution CLI flags.
type AWFContainerConfig struct {
ContainerRuntime string `json:"containerRuntime,omitempty"`

// ImageTag is the pinned AWF Docker image tag, with optional digest metadata.
// Format: "<tag>" or "<tag>,squid=sha256:...,agent=sha256:..."
// Maps to: --image-tag <value>
Expand Down
16 changes: 16 additions & 0 deletions pkg/workflow/awf_config_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,22 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) { //nolint:larg

if isCloudHypervisorRuntime(config.WorkflowData) {
awfConfig.CloudHypervisor = buildAWFCloudHypervisorConfig()
} else if hasCloudHypervisorEnclaves(config.WorkflowData) {
if awfConfig.Container == nil {
awfConfig.Container = &AWFContainerConfig{}
}
awfConfig.Container.ContainerRuntime = "docker"
awfConfig.CloudHypervisor = &AWFCloudHypervisorConfig{
PreviewEnabled: true,
MountPolicy: "workspace-only",
CloudHypervisorBinary: "${GH_AW_CLOUD_HYPERVISOR_BINARY}",
KernelPath: "${GH_AW_CLOUD_HYPERVISOR_KERNEL}",
RootfsPath: "${GH_AW_CLOUD_HYPERVISOR_ROOTFS}",
SupervisorPath: "${GH_AW_CLOUD_HYPERVISOR_SUPERVISOR}",
ArtifactManifestPath: "${GH_AW_CLOUD_HYPERVISOR_ARTIFACT_MANIFEST}",
ArtifactManifestBundlePath: "${GH_AW_CLOUD_HYPERVISOR_ARTIFACT_MANIFEST_BUNDLE}",
ArtifactReleaseTag: "${GH_AW_CLOUD_HYPERVISOR_ARTIFACT_RELEASE_TAG}",
}
}

// ── Logging section ──────────────────────────────────────────────────────
Expand Down
16 changes: 16 additions & 0 deletions pkg/workflow/awf_config_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,28 @@ func validateAWFConfigJSON(configJSON string) error {
return fmt.Errorf("invalid AWF config JSON: expected generated output to be valid JSON for schema validation; parse error: %w. This indicates a compiler bug; please report it", err)
}
normalizeTemplatableModelFallbackEnabled(doc)
normalizeCloudHypervisorBundleReleaseTag(doc)
if err := schema.Validate(doc); err != nil {
return fmt.Errorf("invalid AWF config JSON: expected generated output to satisfy the embedded schema; review the referenced field path and fix that workflow/frontmatter value: %w", err)
}
return nil
}

// The bundle setup resolves the release tag before AWF consumes the config.
func normalizeCloudHypervisorBundleReleaseTag(doc any) {
root, ok := doc.(map[string]any)
if !ok {
return
}
config, ok := root["cloudHypervisor"].(map[string]any)
if !ok {
return
}
if config["artifactReleaseTag"] == "${GH_AW_CLOUD_HYPERVISOR_ARTIFACT_RELEASE_TAG}" {
config["artifactReleaseTag"] = string(constants.DefaultFirewallVersion)
}
}

// normalizeTemplatableModelFallbackEnabled adjusts a generated AWF config document
// for compile-time schema validation by coercing modelFallback.enabled GitHub Actions
// expressions to a boolean placeholder. GitHub Actions resolves these expressions at
Expand Down
Loading
Loading