From c9453fec3952bc515ffd3e264aff66bf1230bc68 Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 09:15:06 -0700 Subject: [PATCH 1/8] Harden compiler bootstrap against compromised certificate authorities Authenticate release checksum manifests with trusted action pins, enforce HTTPS-only bootstrap redirects, require patched Go release toolchains, and document runtime trust ownership and emergency distrust procedures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .changeset/compiler-transport-trust.md | 7 + CHANGELOG.md | 10 + Makefile | 4 + actions/setup/setup.sh | 2 + .../setup/sh/cloud_hypervisor_setup_bundle.sh | 8 +- ...loud_hypervisor_setup_enclave_artifacts.sh | 2 +- actions/setup/sh/install_awf_binary.sh | 25 ++- actions/setup/sh/install_awf_binary_test.sh | 19 +- actions/setup/sh/install_copilot_cli.sh | 19 +- actions/setup/sh/install_gh_cli.sh | 11 +- .../setup/sh/install_threat_detect_binary.sh | 2 +- .../sh/install_threat_detect_binary_test.sh | 8 + actions/setup/sh/release_integrity.sh | 48 +++++ actions/setup/sh/release_integrity_test.sh | 172 +++++++++++++++++ actions/setup/sh/runtime_trust_inventory.sh | 34 ++++ go.mod | 4 +- pkg/cli/install_copilot_cli_test.go | 53 ++++-- pkg/constants/version_constants.go | 2 +- pkg/workflow/copilot_engine_installation.go | 10 +- pkg/workflow/firewall_version_pinning_test.go | 48 +++++ pkg/workflow/runtime_integration_test.go | 5 +- pkg/workflow/runtime_setup_test.go | 2 +- pkg/workflow/tls_trust_conformance_test.go | 174 ++++++++++++++++++ scripts/build-release.sh | 11 +- scripts/check-go-toolchain.sh | 17 ++ scripts/check-go-toolchain_test.sh | 23 +++ scripts/update-threat-detect-pins.sh | 4 +- specs/compiler-threat-detection-changelog.md | 1 + specs/compiler-threat-detection-spec.md | 7 +- specs/security-architecture-spec.md | 49 ++++- 30 files changed, 726 insertions(+), 55 deletions(-) create mode 100644 .changeset/compiler-transport-trust.md create mode 100644 actions/setup/sh/release_integrity.sh create mode 100644 actions/setup/sh/release_integrity_test.sh create mode 100644 actions/setup/sh/runtime_trust_inventory.sh create mode 100644 pkg/workflow/tls_trust_conformance_test.go create mode 100644 scripts/check-go-toolchain.sh create mode 100644 scripts/check-go-toolchain_test.sh diff --git a/.changeset/compiler-transport-trust.md b/.changeset/compiler-transport-trust.md new file mode 100644 index 00000000000..8e2d05fc53a --- /dev/null +++ b/.changeset/compiler-transport-trust.md @@ -0,0 +1,7 @@ +--- +"gh-aw": major +--- + +Harden executable bootstrap against compromised certificate authorities with trusted release-manifest digests, HTTPS-only redirects, pinned Compose and apt-key bytes, patched Go release toolchains, and bounded runtime TLS inventory. + +**Breaking change:** Unreviewed AWF/Copilot versions and `latest` downloads fail closed. Use a reviewed pinned release or add independently authenticated, release-bound manifest digests to the trusted setup action's `release_integrity.sh` and publish that action through the normal release process. Trusted runner toolcache selection remains unchanged. See security architecture §6.8 for emergency distrust and per-runtime ownership. diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e24d9c105..6f4af769eed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ All notable changes to this project will be documented in this file. ### Breaking changes +AWF and downloaded Copilot CLI releases now require a reviewed checksum-manifest +digest bundled in the trusted setup action. Unreviewed custom versions and +`latest` downloads fail closed instead of trusting a checksum from the same +HTTPS origin. Use a reviewed pinned release (AWF v0.28.62 or Copilot CLI +v1.0.98; the legacy Copilot v1.0.21 manifest remains available), or add the +release-bound digest to `actions/setup/sh/release_integrity.sh` through a trusted +setup-action release. Update/recompile workflows to consume that action. +Trusted runner toolcache selection is unchanged. Go release builds require +1.26.10, 1.27.3, or a newer supported stable toolchain. + Logs episode rollups now require a recorded nonempty `context.episode_id`. Call-ID grouping, synthetic episode IDs, metadata-inferred lineage, and bridges through uncorrelated runs are removed. Runs without canonical identity remain diff --git a/Makefile b/Makefile index 6659765300a..0b92a9be5ba 100644 --- a/Makefile +++ b/Makefile @@ -531,12 +531,16 @@ test-scripts: build bash scripts/check-workflow-drift_test.sh ./$(BINARY_NAME) bash scripts/check-cgo-cjs-workflow-purity_test.sh bash scripts/update-threat-detect-pins_test.sh + bash scripts/check-go-toolchain_test.sh python3 scripts/test_super_linter_scope.py python3 scripts/test_publish_safe_outputs_node.py bash actions/setup/sh/collect_usage_artifact_files_test.sh bash actions/setup/sh/copy_gh_aw_binary_for_mcp_test.sh bash actions/setup/sh/create_gh_aw_tmp_dir_test.sh bash actions/setup/sh/download_docker_images_local_test.sh + bash actions/setup/sh/install_awf_binary_test.sh + bash actions/setup/sh/install_threat_detect_binary_test.sh + bash actions/setup/sh/release_integrity_test.sh @echo "✓ All script tests passed" # Test all code and guidance (Go, JavaScript, wasm golden, shell scripts, and guidance contracts) diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 9b787f08a11..8c25a09c194 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -210,6 +210,8 @@ fi echo "Successfully copied ${FILE_COUNT} files to ${DESTINATION}" +bash "${DESTINATION}/runtime_trust_inventory.sh" + # Copy prompt markdown files to their expected directory PROMPTS_DEST="${GH_AW_ROOT}/prompts" debug_log "Copying prompt markdown files to ${PROMPTS_DEST}" diff --git a/actions/setup/sh/cloud_hypervisor_setup_bundle.sh b/actions/setup/sh/cloud_hypervisor_setup_bundle.sh index 146cdcf819c..56cfdf953ee 100755 --- a/actions/setup/sh/cloud_hypervisor_setup_bundle.sh +++ b/actions/setup/sh/cloud_hypervisor_setup_bundle.sh @@ -29,7 +29,7 @@ if [[ "${version,,}" == "latest" ]]; then elif [[ -n "${GITHUB_TOKEN:-}" ]]; then auth_header=(-H "Authorization: token ${GITHUB_TOKEN}") fi - if ! latest_release_json="$(curl -fsSL "${auth_header[@]}" -H "Accept: application/vnd.github+json" \ + if ! latest_release_json="$(curl --proto '=https' --proto-redir '=https' -fsSL "${auth_header[@]}" -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/github/gh-aw-firewall/releases/latest")"; then echo "::error::failed to resolve the latest gh-aw-firewall release tag: could not reach the GitHub releases API" exit 1 @@ -55,9 +55,9 @@ 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}/${manifest_name}" "${asset_base_url}/${manifest_name}" -curl -fsSL -o "${bundle_root}/${manifest_bundle_name}" "${asset_base_url}/${manifest_bundle_name}" +curl --proto '=https' --proto-redir '=https' -fsSL -o "${bundle_root}/${asset_name}" "${asset_base_url}/${asset_name}" +curl --proto '=https' --proto-redir '=https' -fsSL -o "${bundle_root}/${manifest_name}" "${asset_base_url}/${manifest_name}" +curl --proto '=https' --proto-redir '=https' -fsSL -o "${bundle_root}/${manifest_bundle_name}" "${asset_base_url}/${manifest_bundle_name}" echo "downloaded release assets" echo "::endgroup::" diff --git a/actions/setup/sh/cloud_hypervisor_setup_enclave_artifacts.sh b/actions/setup/sh/cloud_hypervisor_setup_enclave_artifacts.sh index 91aa2528ab2..0073430742d 100644 --- a/actions/setup/sh/cloud_hypervisor_setup_enclave_artifacts.sh +++ b/actions/setup/sh/cloud_hypervisor_setup_enclave_artifacts.sh @@ -35,7 +35,7 @@ source_commit="$(jq -er --arg tag "${version}" \ echo "::error::invalid enclave artifact source commit" exit 1 } -curl -fsSL \ +curl --proto '=https' --proto-redir '=https' -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" diff --git a/actions/setup/sh/install_awf_binary.sh b/actions/setup/sh/install_awf_binary.sh index 6305a70d2fe..04d2c1da329 100755 --- a/actions/setup/sh/install_awf_binary.sh +++ b/actions/setup/sh/install_awf_binary.sh @@ -20,12 +20,16 @@ set +o histexpand # # Security features: # - Downloads directly from GitHub releases -# - Verifies SHA256 checksum against official checksums.txt +# - Authenticates checksums.txt against a digest bundled in the trusted action # - Fails fast if checksum verification fails # - Eliminates trust dependency on installer scripts set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=actions/setup/sh/release_integrity.sh +source "${SCRIPT_DIR}/release_integrity.sh" + # Configuration AWF_VERSION="${1:-}" AWF_REPO="github/gh-aw-firewall" @@ -66,6 +70,12 @@ if [ -z "$AWF_VERSION" ]; then echo "Usage: $0 VERSION [--rootless]" exit 1 fi +AWF_VERSION="v${AWF_VERSION#v}" +if [[ ! "$AWF_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: AWF version must be a pinned semantic release tag" >&2 + exit 1 +fi +CHECKSUMS_SHA256="$(release_artifact_sha256 "$AWF_REPO" "$AWF_VERSION" checksums.txt)" # Detect OS and architecture OS="$(uname -s)" @@ -102,7 +112,7 @@ fi # Download URLs BASE_URL="https://github.com/${AWF_REPO}/releases/download/${AWF_VERSION}" CHECKSUMS_URL="${BASE_URL}/checksums.txt" -CURL_RETRY_OPTS=(--retry 5 --retry-delay 10 --retry-max-time 180) +CURL_RETRY_OPTS=(--proto '=https' --proto-redir '=https' --retry 5 --retry-delay 10 --retry-max-time 180) # --retry-all-errors was added in curl 7.71, so it must not be passed to older curl # builds found on some self-hosted runners. Probe the help output instead of parsing # `curl --version`, which reports vendor-patched version strings that do not reliably @@ -134,16 +144,17 @@ trap 'rm -rf "$TEMP_DIR"' EXIT # Download checksums echo "Downloading checksums from ${CHECKSUMS_URL@Q}..." curl -fsSL "${CURL_RETRY_OPTS[@]}" -o "${TEMP_DIR}/checksums.txt" "${CHECKSUMS_URL}" +verify_release_digest "${TEMP_DIR}/checksums.txt" "$CHECKSUMS_SHA256" verify_checksum() { local file="$1" local fname="$2" echo "Verifying SHA256 checksum for ${fname}..." - EXPECTED_CHECKSUM=$(awk -v fname="${fname}" '$2 == fname {print $1; exit}' "${TEMP_DIR}/checksums.txt" | tr 'A-F' 'a-f') + EXPECTED_CHECKSUM=$(awk -v fname="${fname}" '$2 == fname || $2 == "*" fname {count++; digest=$1} END {if (count == 1) print digest}' "${TEMP_DIR}/checksums.txt" | tr 'A-F' 'a-f') - if [ -z "$EXPECTED_CHECKSUM" ]; then - echo "ERROR: Could not find checksum for ${fname} in checksums.txt" + if [[ ! "$EXPECTED_CHECKSUM" =~ ^[0-9a-f]{64}$ ]]; then + echo "ERROR: Expected exactly one valid checksum for ${fname} in authenticated checksums.txt" >&2 return 1 fi @@ -192,8 +203,8 @@ install_bundle() { # Verify checksum if ! verify_checksum "${TEMP_DIR}/${bundle_name}" "${bundle_name}"; then - echo "⚠ Bundle checksum verification failed" - return 1 + echo "ERROR: Bundle checksum verification failed; refusing fallback" >&2 + exit 1 fi # Install bundle to lib directory diff --git a/actions/setup/sh/install_awf_binary_test.sh b/actions/setup/sh/install_awf_binary_test.sh index d190b550fa6..7a335af598c 100755 --- a/actions/setup/sh/install_awf_binary_test.sh +++ b/actions/setup/sh/install_awf_binary_test.sh @@ -5,6 +5,8 @@ set +o histexpand # Run: bash install_awf_binary_test.sh SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=actions/setup/sh/release_integrity.sh +source "${SCRIPT_DIR}/release_integrity.sh" TESTS_PASSED=0 TESTS_FAILED=0 @@ -107,7 +109,7 @@ test_curl_retry_all_errors() { local test_dir local option_index local -a actual_options - local -a expected_options=(-fsSL --retry 5 --retry-delay 10 --retry-max-time 180) + local -a expected_options=(-fsSL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 10 --retry-max-time 180) TEST_FAILURE_REASON="" if [ "${expected}" = true ]; then expected_options+=(--retry-all-errors) @@ -140,7 +142,7 @@ while [ "$#" -gt 0 ]; do shift done if [[ "${output}" == *checksums.txt ]]; then - printf 'checksum awf-linux-x64\n' > "${output}" + printf '%064d awf-linux-x64\n' 0 > "${output}" else printf '#!/usr/bin/env bash\nexit 0\n' > "${output}" fi @@ -151,7 +153,11 @@ echo v18.0.0 EOF cat > "${test_dir}/bin/sha256sum" <<'EOF' #!/usr/bin/env bash -printf 'checksum %s\n' "$1" +if [[ "$1" == *checksums.txt ]]; then + printf '%s %s\n' "$AWF_TEST_MANIFEST_SHA256" "$1" +else + printf '%064d %s\n' 0 "$1" +fi EOF cat > "${test_dir}/bin/uname" <<'EOF' #!/usr/bin/env bash @@ -160,10 +166,13 @@ case "$1" in -m) echo x86_64 ;; esac EOF + printf '#!/usr/bin/env bash\nexit 0\n' >"${test_dir}/bin/sudo" + printf '#!/usr/bin/env bash\nexit 0\n' >"${test_dir}/bin/find" chmod +x "${test_dir}/bin/"* - if ! CURL_HELP_ALL_OUTPUT="${curl_help_all}" CURL_HELP_OUTPUT="${curl_help}" CURL_HELP_ALL_FAILS="${help_all_fails}" CURL_ARGS_FILE="${test_dir}/curl-args" \ + if ! AWF_TEST_MANIFEST_SHA256="$(release_artifact_sha256 github/gh-aw-firewall v0.28.62 checksums.txt)" \ + CURL_HELP_ALL_OUTPUT="${curl_help_all}" CURL_HELP_OUTPUT="${curl_help}" CURL_HELP_ALL_FAILS="${help_all_fails}" CURL_ARGS_FILE="${test_dir}/curl-args" \ HOME="${test_dir}/home" GITHUB_PATH="${test_dir}/github-path" \ - PATH="${test_dir}/bin:/usr/bin:/bin" bash "${SCRIPT_DIR}/install_awf_binary.sh" vtest --rootless >/dev/null; then + PATH="${test_dir}/bin:${PATH}" bash "${SCRIPT_DIR}/install_awf_binary.sh" v0.28.62 --rootless >/dev/null; then TEST_FAILURE_REASON="installer exited unsuccessfully" rm -rf "${test_dir}" return 1 diff --git a/actions/setup/sh/install_copilot_cli.sh b/actions/setup/sh/install_copilot_cli.sh index 2df7729e46f..f213512359f 100755 --- a/actions/setup/sh/install_copilot_cli.sh +++ b/actions/setup/sh/install_copilot_cli.sh @@ -21,7 +21,7 @@ set +o histexpand # # Security features: # - Downloads binary directly from GitHub releases (no installer script execution) -# - Verifies SHA256 checksum against official SHA256SUMS.txt +# - Authenticates SHA256SUMS.txt against digests bundled in the trusted action # - Fails fast if checksum verification fails set -euo pipefail @@ -42,6 +42,8 @@ COMPILED_GH_AW_VERSION="${GH_AW_COMPILED_VERSION:-}" COPILOT_MIN_VERSION_OVERRIDE="${GH_AW_COPILOT_MIN_VERSION:-}" COPILOT_GUEST_LIBC="${GH_AW_COPILOT_GUEST_LIBC:-}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=actions/setup/sh/release_integrity.sh +source "${SCRIPT_DIR}/release_integrity.sh" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" COMPAT_BUNDLED_PATH="${COPILOT_COMPAT_BUNDLED_PATH:-${REPO_ROOT}/.github/aw/compat.json}" COMPAT_MATCHED_MIN_AGENT="" @@ -294,7 +296,7 @@ download_compat_json() { local source_file="$2" echo "Attempting to download compatibility matrix from ${COMPAT_URL}..." >&2 - if curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors -o "$compat_file" "$COMPAT_URL"; then + if curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 --retry-delay 5 --retry-all-errors -o "$compat_file" "$COMPAT_URL"; then echo "$COMPAT_URL" > "$source_file" echo "Successfully downloaded compatibility matrix from ${COMPAT_URL}" >&2 return 0 @@ -615,6 +617,7 @@ stage_musl_guest_copilot() { local actual_checksum="" local extract_dir="${TEMP_DIR}/guest-copilot" local destination="" + local manifest_digest="" if [ "$COPILOT_GUEST_LIBC" != "musl" ]; then return 0 @@ -640,9 +643,11 @@ stage_musl_guest_copilot() { guest_tarball="copilot-linuxmusl-${ARCH_NAME}.tar.gz" guest_archive="${TEMP_DIR}/${guest_tarball}" + manifest_digest="$(release_artifact_sha256 "$COPILOT_REPO" "$version" SHA256SUMS.txt)" || return 1 echo "Staging checksum-verified ${guest_tarball} for the musl NVX guest..." - curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "$checksums_file" "${base_url}/SHA256SUMS.txt" - curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "$guest_archive" "${base_url}/${guest_tarball}" + curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "$checksums_file" "${base_url}/SHA256SUMS.txt" + verify_release_digest "$checksums_file" "$manifest_digest" || return 1 + curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "$guest_archive" "${base_url}/${guest_tarball}" expected_checksum="$(awk -v fname="$guest_tarball" '$2 == fname {print $1; exit}' "$checksums_file" | tr 'A-F' 'a-f')" if [ -z "$expected_checksum" ]; then @@ -739,14 +744,16 @@ fi TARBALL_URL="${BASE_URL}/${TARBALL_NAME}" CHECKSUMS_URL="${BASE_URL}/SHA256SUMS.txt" +CHECKSUMS_SHA256="$(release_artifact_sha256 "$COPILOT_REPO" "$VERSION" SHA256SUMS.txt)" # Download checksums echo "Downloading checksums from ${CHECKSUMS_URL}..." -curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}" +curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}" +verify_release_digest "${TEMP_DIR}/SHA256SUMS.txt" "$CHECKSUMS_SHA256" # Download binary tarball echo "Downloading binary from ${TARBALL_URL}..." -curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}" +curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}" # Verify checksum echo "Verifying SHA256 checksum for ${TARBALL_NAME}..." diff --git a/actions/setup/sh/install_gh_cli.sh b/actions/setup/sh/install_gh_cli.sh index fe619e5dd01..c508db4fe05 100644 --- a/actions/setup/sh/install_gh_cli.sh +++ b/actions/setup/sh/install_gh_cli.sh @@ -44,8 +44,15 @@ KEYRING_PATH="/usr/share/keyrings/githubcli-archive-keyring.gpg" SOURCE_LIST="/etc/apt/sources.list.d/github-cli.list" echo "Adding GitHub CLI apt repository..." -curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - | sudo dd of="${KEYRING_PATH}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=actions/setup/sh/release_integrity.sh +source "${SCRIPT_DIR}/release_integrity.sh" +key_file="$(mktemp)" +trap 'rm -f "$key_file"' EXIT +curl --proto '=https' --proto-redir '=https' -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + -o "$key_file" +verify_release_digest "$key_file" 6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b +sudo cp "$key_file" "${KEYRING_PATH}" sudo chmod go+r "${KEYRING_PATH}" echo "deb [arch=$(dpkg --print-architecture) signed-by=${KEYRING_PATH}] https://cli.github.com/packages stable main" \ diff --git a/actions/setup/sh/install_threat_detect_binary.sh b/actions/setup/sh/install_threat_detect_binary.sh index e5fe6f0d5c7..3ef3ddcc4df 100755 --- a/actions/setup/sh/install_threat_detect_binary.sh +++ b/actions/setup/sh/install_threat_detect_binary.sh @@ -227,7 +227,7 @@ install_linux_binary() { local binary_url="${BASE_URL}/${binary_name}" echo "Downloading binary from \"${binary_url}\"..." - curl -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 --retry-all-errors -o "${TEMP_DIR}/${binary_name}" "${binary_url}" + curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 10 --retry-max-time 180 --retry-all-errors -o "${TEMP_DIR}/${binary_name}" "${binary_url}" # Verify checksum verify_checksum "${TEMP_DIR}/${binary_name}" "${binary_name}" "${expected_checksum}" diff --git a/actions/setup/sh/install_threat_detect_binary_test.sh b/actions/setup/sh/install_threat_detect_binary_test.sh index e37043ad592..141c6a2dbff 100755 --- a/actions/setup/sh/install_threat_detect_binary_test.sh +++ b/actions/setup/sh/install_threat_detect_binary_test.sh @@ -40,14 +40,22 @@ EOF out="" url="" prev="" +https_only=false +https_redirects=false for arg in "$@"; do if [ "$prev" = "-o" ]; then out="$arg" elif [ "${arg#-}" = "$arg" ]; then url="$arg" fi + if [ "$prev" = "--proto" ] && [ "$arg" = "=https" ]; then https_only=true; fi + if [ "$prev" = "--proto-redir" ] && [ "$arg" = "=https" ]; then https_redirects=true; fi prev="$arg" done +if [ "$https_only" != true ] || [ "$https_redirects" != true ]; then + echo "missing HTTPS-only initial/redirect policy" >&2 + exit 96 +fi echo "$url" >>"${SANDBOX_URL_LOG}" printf '%s\n' '#!/usr/bin/env bash' 'echo verified >>"${SANDBOX_EXECUTION_LOG}"' >"$out" EOF diff --git a/actions/setup/sh/release_integrity.sh b/actions/setup/sh/release_integrity.sh new file mode 100644 index 00000000000..bf3fc1c88b3 --- /dev/null +++ b/actions/setup/sh/release_integrity.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set +o histexpand + +# Sourced by installers from the trusted setup action, never from the download origin. +# Review new pins against authenticated release provenance before committing them. +# Each entry binds repository, immutable release version, asset name, and SHA256. +release_artifact_sha256() { + local repository="$1" version="$2" asset="$3" digest + version="v${version#v}" + digest=$(awk -v repository="$repository" -v version="$version" -v asset="$asset" ' + $1 == repository && $2 == version && $3 == asset { count++; digest = $4 } + END { if (count == 1) print digest; else exit 1 } + ' <<'PINS' +github/gh-aw-firewall v0.28.62 checksums.txt 0b03f6ea5b638356a799fa541ade4fd7902a6ac8d7c3f0252ee09d8259a4ce8f +github/copilot-cli v1.0.98 SHA256SUMS.txt 372f0c8d3d50076455cd19ef080dc59dc2a32401d8b2d4f77ce190c8ab13bc59 +github/copilot-cli v1.0.21 SHA256SUMS.txt fd9b2fb92a39aeb2a41815a6aa0bf3853307c812b3451a45f5a2217be003987d +PINS + ) || { + echo "ERROR: No reviewed SHA256 pin for ${repository} ${version} ${asset}; update the trusted setup action's release_integrity.sh before installing this release" >&2 + return 1 + } + if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then + echo "ERROR: Invalid reviewed SHA256 pin for ${repository} ${version} ${asset}" >&2 + return 1 + fi + printf '%s\n' "$digest" +} + +verify_release_digest() { + local file="$1" expected="$2" actual + if [[ ! "$expected" =~ ^[0-9a-f]{64}$ ]]; then + echo "ERROR: A trusted lowercase SHA256 digest is required" >&2 + return 1 + fi + if command -v sha256sum >/dev/null 2>&1; then + actual=$(sha256sum "$file") || return 1 + elif command -v shasum >/dev/null 2>&1; then + actual=$(shasum -a 256 "$file") || return 1 + else + echo "ERROR: No sha256sum or shasum found" >&2 + return 1 + fi + actual="${actual%% *}" + if [ "$actual" != "$expected" ]; then + echo "ERROR: Trusted SHA256 verification failed for $(basename "$file"); refusing installation" >&2 + return 1 + fi +} diff --git a/actions/setup/sh/release_integrity_test.sh b/actions/setup/sh/release_integrity_test.sh new file mode 100644 index 00000000000..c022dc1e871 --- /dev/null +++ b/actions/setup/sh/release_integrity_test.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TEMP_DIR"' EXIT +# shellcheck source=actions/setup/sh/release_integrity.sh +source "$SCRIPT_DIR/release_integrity.sh" + +mkdir -p "$TEMP_DIR/bin" "$TEMP_DIR/home" "$TEMP_DIR/scripts" "$TEMP_DIR/copilot-fixture" +cp "$SCRIPT_DIR/install_awf_binary.sh" "$TEMP_DIR/scripts/" +cp "$SCRIPT_DIR/install_copilot_cli.sh" "$TEMP_DIR/scripts/" +printf '#!/usr/bin/env bash\necho EXECUTED >>"$EXECUTION_LOG"\n' >"$TEMP_DIR/payload" +payload_digest="$(shasum -a 256 "$TEMP_DIR/payload" | awk '{print $1}')" +for asset in awf-bundle.js awf-linux-x64 awf-linux-arm64 awf-darwin-x64 awf-darwin-arm64; do + printf '%s %s\n' "$payload_digest" "$asset" +done >"$TEMP_DIR/checksums.txt" +cp "$TEMP_DIR/checksums.txt" "$TEMP_DIR/valid-checksums.txt" +manifest_digest="$(shasum -a 256 "$TEMP_DIR/checksums.txt" | awk '{print $1}')" +cp "$TEMP_DIR/payload" "$TEMP_DIR/copilot-fixture/copilot" +chmod +x "$TEMP_DIR/copilot-fixture/copilot" +tar -czf "$TEMP_DIR/copilot-linux-x64.tar.gz" -C "$TEMP_DIR/copilot-fixture" copilot +archive_digest="$(shasum -a 256 "$TEMP_DIR/copilot-linux-x64.tar.gz" | awk '{print $1}')" +printf '%s copilot-linux-x64.tar.gz\n' "$archive_digest" >"$TEMP_DIR/SHA256SUMS.txt" +copilot_digest="$(shasum -a 256 "$TEMP_DIR/SHA256SUMS.txt" | awk '{print $1}')" +# Test pins live only beside copies of the real installers, never in production inputs. +sed -e "s/0b03f6ea5b638356a799fa541ade4fd7902a6ac8d7c3f0252ee09d8259a4ce8f/$manifest_digest/" \ + -e "s/372f0c8d3d50076455cd19ef080dc59dc2a32401d8b2d4f77ce190c8ab13bc59/$copilot_digest/" \ + "$SCRIPT_DIR/release_integrity.sh" >"$TEMP_DIR/scripts/release_integrity.sh" + +cat >"$TEMP_DIR/bin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +if [ "${1:-}" = "--help" ]; then exit 0; fi +printf '%s\n' "$*" >>"$TEST_ROOT/curl.log" +out="" +https_only=false +https_redirects=false +while [ "$#" -gt 0 ]; do + case "$1" in + --proto) + [ "$2" = '=https' ] + https_only=true + shift 2 ;; + --proto-redir) + [ "$2" = '=https' ] + https_redirects=true + shift 2 ;; + -o) out="$2"; shift 2 ;; + *) url="$1"; shift ;; + esac +done +if [ "$https_only" != true ] || [ "$https_redirects" != true ]; then + echo "missing HTTPS-only initial/redirect policy" >&2; exit 96 +fi +case "$url" in + */checksums.txt) cp "$TEST_ROOT/checksums.txt" "$out" ;; + */SHA256SUMS.txt) cp "$TEST_ROOT/SHA256SUMS.txt" "$out" ;; + */awf-bundle.js|*/awf-linux-*|*/awf-darwin-*) cp "$TEST_ROOT/payload" "$out" ;; + */copilot-linux-x64.tar.gz) cp "$TEST_ROOT/copilot-linux-x64.tar.gz" "$out" ;; + *) echo "unexpected download: $url" >&2; exit 97 ;; +esac +EOF +cat >"$TEMP_DIR/bin/node" <<'EOF' +#!/usr/bin/env bash +if [ "$1" = "--version" ]; then echo "${AWF_TEST_NODE_VERSION:-v24.0.0}"; else echo EXECUTED >>"$EXECUTION_LOG"; fi +EOF +cat >"$TEMP_DIR/bin/uname" <<'EOF' +#!/usr/bin/env bash +case "$1" in -s) echo "${AWF_TEST_OS:-Linux}" ;; -m) echo "${AWF_TEST_ARCH:-x86_64}" ;; esac +EOF +for command in sudo find; do + printf '#!/usr/bin/env bash\nexit 0\n' >"$TEMP_DIR/bin/$command" +done +chmod +x "$TEMP_DIR/bin/"* + +run_awf() { + env TEST_ROOT="$TEMP_DIR" EXECUTION_LOG="$TEMP_DIR/executions" HOME="$TEMP_DIR/home" \ + GITHUB_PATH="$TEMP_DIR/github-path" PATH="$TEMP_DIR/bin:$PATH" \ + bash "$TEMP_DIR/scripts/install_awf_binary.sh" "$@" >"$TEMP_DIR/output" 2>&1 +} +run_copilot() { + env TEST_ROOT="$TEMP_DIR" EXECUTION_LOG="$TEMP_DIR/executions" HOME="$TEMP_DIR/home" \ + GITHUB_PATH="$TEMP_DIR/github-path" RUNNER_TOOL_CACHE="$TEMP_DIR/empty-cache" \ + GH_AW_COMPILED_VERSION="" PATH="$TEMP_DIR/bin:$PATH" \ + bash "$TEMP_DIR/scripts/install_copilot_cli.sh" "$@" >"$TEMP_DIR/output" 2>&1 +} + +run_awf v0.28.62 --rootless +test -x "$TEMP_DIR/home/.local/bin/awf" +grep -q EXECUTED "$TEMP_DIR/executions" +echo "PASS: authenticated AWF bundle installs and executes" +run_copilot 1.0.98 --rootless +test -x "$TEMP_DIR/home/.local/bin/copilot" +rm -f "$TEMP_DIR/home/.local/bin/copilot" +echo "PASS: authenticated Copilot archive installs and executes" +for platform in Linux Darwin; do + for arch in x86_64 arm64; do + AWF_TEST_NODE_VERSION=v18.0.0 AWF_TEST_OS="$platform" AWF_TEST_ARCH="$arch" run_awf v0.28.62 --rootless + test -x "$TEMP_DIR/home/.local/bin/awf" + done +done +echo "PASS: authenticated AWF fallback binaries cover Linux/macOS x64/arm64" + +# Replacing the manifest and executable together must fail before any execution. +rm -f "$TEMP_DIR/executions" "$TEMP_DIR/curl.log" +printf 'attacker payload\n' >"$TEMP_DIR/payload" +attacker_digest="$(shasum -a 256 "$TEMP_DIR/payload" | awk '{print $1}')" +printf '%s awf-bundle.js\n' "$attacker_digest" >"$TEMP_DIR/checksums.txt" +if run_awf v0.28.62 --rootless; then + echo "FAIL: AWF accepted artifact-plus-manifest replacement" >&2; exit 1 +fi +grep -q 'Trusted SHA256 verification failed' "$TEMP_DIR/output" +test ! -e "$TEMP_DIR/executions" +test "$(wc -l <"$TEMP_DIR/curl.log" | tr -d ' ')" = 1 +echo "PASS: AWF rejects artifact-plus-manifest replacement before downloading executable" + +# An authentic manifest must not allow a tampered bundle to trigger a binary fallback. +cp "$TEMP_DIR/valid-checksums.txt" "$TEMP_DIR/checksums.txt" +rm -f "$TEMP_DIR/curl.log" +if run_awf v0.28.62 --rootless; then + echo "FAIL: AWF accepted a tampered bundle" >&2; exit 1 +fi +grep -q 'refusing fallback' "$TEMP_DIR/output" +test ! -e "$TEMP_DIR/executions" +test "$(wc -l <"$TEMP_DIR/curl.log" | tr -d ' ')" = 2 +echo "PASS: AWF bundle integrity failure cannot fall back" + +printf '%s copilot-linux-x64.tar.gz\n' "$attacker_digest" >"$TEMP_DIR/SHA256SUMS.txt" +rm -f "$TEMP_DIR/curl.log" +if run_copilot 1.0.98 --rootless; then + echo "FAIL: Copilot accepted artifact-plus-manifest replacement" >&2; exit 1 +fi +grep -q 'Trusted SHA256 verification failed' "$TEMP_DIR/output" +test "$(wc -l <"$TEMP_DIR/curl.log" | tr -d ' ')" = 1 +test ! -e "$TEMP_DIR/home/.local/bin/copilot" +echo "PASS: Copilot authenticates its manifest before downloading or extracting executable" + +rm -f "$TEMP_DIR/curl.log" +if run_awf latest --rootless || run_awf v9.9.9 --rootless; then + echo "FAIL: AWF accepted an unreviewed release" >&2; exit 1 +fi +test ! -e "$TEMP_DIR/curl.log" +if run_copilot latest --rootless; then + echo "FAIL: Copilot accepted an unreviewed latest download" >&2; exit 1 +fi +test ! -e "$TEMP_DIR/curl.log" +echo "PASS: unreviewed/latest release downloads fail before network access" + +test -n "$(release_artifact_sha256 github/gh-aw-firewall v0.28.62 checksums.txt)" +if release_artifact_sha256 github/gh-aw-firewall v0.28.62 wrong-asset >/dev/null 2>&1; then + echo "FAIL: digest pin was not asset-bound" >&2; exit 1 +fi +if verify_release_digest "$TEMP_DIR/payload" invalid >/dev/null 2>&1; then + echo "FAIL: malformed trusted digest accepted" >&2; exit 1 +fi +echo "PASS: trusted pins are asset-bound and validated" + +awk '{print} /^github\/gh-aw-firewall / {print}' "$SCRIPT_DIR/release_integrity.sh" >"$TEMP_DIR/duplicate-pins.sh" +if bash -c 'source "$1"; release_artifact_sha256 github/gh-aw-firewall v0.28.62 checksums.txt' -- "$TEMP_DIR/duplicate-pins.sh" >"$TEMP_DIR/output" 2>&1; then + echo "FAIL: duplicate trusted pins accepted" >&2; exit 1 +fi +grep -q 'No reviewed SHA256 pin' "$TEMP_DIR/output" +echo "PASS: duplicate trusted release entries fail closed" + +# Real curl enforces the protocol policy, independent of the download mock. +if curl --proto '=https' --proto-redir '=https' -fsSL --max-time 2 http://127.0.0.1:1 >"$TEMP_DIR/curl-output" 2>&1; then + echo "FAIL: HTTP URL accepted" >&2; exit 1 +fi +grep -qi 'protocol.*disabled' "$TEMP_DIR/curl-output" +echo "PASS: real curl refuses HTTP before connecting" diff --git a/actions/setup/sh/runtime_trust_inventory.sh b/actions/setup/sh/runtime_trust_inventory.sh new file mode 100644 index 00000000000..d3bafb8cb51 --- /dev/null +++ b/actions/setup/sh/runtime_trust_inventory.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +# Version evidence only: do not dump environment variables, proxy URLs or headers. +echo "::group::Runtime TLS inventory (not a trust-store attestation)" +if command -v curl >/dev/null 2>&1; then + curl --version | sed -n '1p' +else + echo "curl: unavailable" +fi +if command -v node >/dev/null 2>&1; then + node -p '"Node " + process.version + "; OpenSSL " + process.versions.openssl' +else + echo "Node: unavailable" +fi +if command -v go >/dev/null 2>&1; then + GOTOOLCHAIN=local go env GOVERSION + for binary in gh gh-aw; do + if command -v "$binary" >/dev/null 2>&1; then + if ! GOTOOLCHAIN=local go version -m "$(command -v "$binary")" | sed -n '1p'; then + echo "WARNING: ${binary} build-toolchain inspection failed; trust status unknown" >&2 + fi + else + echo "${binary}: unavailable for build-toolchain inspection" + fi + done +else + echo "Go: unavailable; binary build-toolchain inspection unavailable" +fi +echo "Python/certifi: not inspected by shell setup; record each client's interpreter, TLS backend and selected CA bundle separately." +echo "Container, proxy and MCP trust stores require separate image/vendor evidence." +echo "Browser updates and successful HTTPS connections do not attest CA distrust." +echo "::endgroup::" diff --git a/go.mod b/go.mod index b9d316a49aa..083c286faf6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/github/gh-aw -go 1.26.9 +go 1.26.10 + +toolchain go1.27.3 require ( charm.land/bubbles/v2 v2.2.1 diff --git a/pkg/cli/install_copilot_cli_test.go b/pkg/cli/install_copilot_cli_test.go index a35c07b67a8..2ca3b16c91a 100644 --- a/pkg/cli/install_copilot_cli_test.go +++ b/pkg/cli/install_copilot_cli_test.go @@ -18,6 +18,19 @@ import ( "github.com/stretchr/testify/require" ) +func prepareCopilotInstallerTestBin(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.MkdirAll(dir, 0o755)) + // Never run the installer's shared-runner cleanup against the test host. + require.NoError(t, os.WriteFile(filepath.Join(dir, "find"), []byte(`#!/usr/bin/env bash +if [ "${1:-}" = /tmp ]; then exit 0; fi +exec /usr/bin/find "$@" +`), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "uname"), []byte(`#!/usr/bin/env bash +case "${1:-}" in -s) echo Linux ;; -m) echo x86_64 ;; *) echo Linux ;; esac +`), 0o755)) +} + func TestInstallCopilotCLIScriptStagesMuslGuestVariant(t *testing.T) { t.Parallel() wd, err := os.Getwd() @@ -43,10 +56,21 @@ func TestInstallCopilotCLIScriptStagesMuslGuestVariant(t *testing.T) { require.NoError(t, os.WriteFile(guestArchive, archive.Bytes(), 0o644)) checksum := sha256.Sum256(archive.Bytes()) checksums := filepath.Join(tempDir, "SHA256SUMS.txt") - require.NoError(t, os.WriteFile(checksums, []byte(fmt.Sprintf("%x copilot-linuxmusl-x64.tar.gz\n", checksum)), 0o644)) + checksumsContent := []byte(fmt.Sprintf("%x copilot-linuxmusl-x64.tar.gz\n", checksum)) + require.NoError(t, os.WriteFile(checksums, checksumsContent, 0o644)) + // Bundle a fixture pin beside a copy of the real installer, not in its environment. + installerContent, err := os.ReadFile(installScript) + require.NoError(t, err) + installScript = filepath.Join(tempDir, "install_copilot_cli.sh") + require.NoError(t, os.WriteFile(installScript, installerContent, 0o755)) + integrityContent, err := os.ReadFile(filepath.Join(projectRoot, "actions", "setup", "sh", "release_integrity.sh")) + require.NoError(t, err) + fixturePin := fmt.Sprintf("github/copilot-cli v1.2.3 SHA256SUMS.txt %x\nPINS", sha256.Sum256(checksumsContent)) + integrityContent = bytes.Replace(integrityContent, []byte("\nPINS"), []byte("\n"+fixturePin), 1) + require.NoError(t, os.WriteFile(filepath.Join(tempDir, "release_integrity.sh"), integrityContent, 0o644)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) require.NoError(t, os.WriteFile(filepath.Join(fakeBinDir, "uname"), []byte(`#!/usr/bin/env bash case "${1:-}" in -s) echo Linux ;; @@ -119,7 +143,7 @@ func TestInstallCopilotCLIScriptUsesToolcacheBeforeDownload(t *testing.T) { require.NoError(t, os.WriteFile(cachedCopilot, []byte("#!/usr/bin/env bash\necho 'copilot 1.2.3'\n"), 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) curlLog := filepath.Join(tempDir, "curl.log") sudoScript := filepath.Join(fakeBinDir, "sudo") @@ -186,7 +210,7 @@ func TestInstallCopilotCLIScriptPreservesCachedBinaryAtInstallPath(t *testing.T) cachedContents := []byte("#!/usr/bin/env bash\necho 'copilot 1.2.3 preserved'\n") require.NoError(t, os.WriteFile(cachedCopilot, cachedContents, 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) require.NoError(t, os.WriteFile(filepath.Join(fakeBinDir, "sudo"), []byte(`#!/usr/bin/env bash if [ "${1:-}" = "chown" ]; then exit 0 @@ -249,7 +273,7 @@ func TestInstallCopilotCLIScriptDevModeUsesToolcache(t *testing.T) { require.NoError(t, os.WriteFile(tooNewCachedCopilot, []byte("#!/usr/bin/env bash\necho 'copilot "+cachedTooNewVersion+"'\n"), 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) curlLog := filepath.Join(tempDir, "curl.log") sudoScript := filepath.Join(fakeBinDir, "sudo") @@ -375,7 +399,7 @@ func TestInstallCopilotCLIScriptAppliesCopilotMinVersionOverrideToToolcache(t *t require.NoError(t, os.WriteFile(minCachedCopilot, []byte("#!/usr/bin/env bash\necho 'copilot "+cachedMinVersion+"'\n"), 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) curlLog := filepath.Join(tempDir, "curl.log") sudoScript := filepath.Join(fakeBinDir, "sudo") @@ -479,7 +503,7 @@ func TestInstallCopilotCLIScriptLatestAppliesCopilotMinVersionToToolcache(t *tes require.NoError(t, os.WriteFile(filepath.Join(binDir, "copilot"), []byte("#!/usr/bin/env bash\necho 'copilot "+version+"'\n"), 0o755)) } fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) require.NoError(t, os.WriteFile(filepath.Join(fakeBinDir, "sudo"), []byte(`#!/usr/bin/env bash if [ "${1:-}" = "chown" ]; then exit 0; fi exec "$@" @@ -508,9 +532,8 @@ exec "$@" } else { require.Error(t, err, "the mocked download must fail: %s", output) assert.NotContains(t, string(output), "Using cached GitHub Copilot CLI") - curlOutput, err := os.ReadFile(curlLog) - require.NoError(t, err) - assert.Contains(t, string(curlOutput), "/releases/latest/download/") + assert.Contains(t, string(output), "No reviewed SHA256 pin") + assert.NoFileExists(t, curlLog, "an unreviewed latest download must fail before network access") assert.NoFileExists(t, filepath.Join(installDir, "copilot")) } }) @@ -527,7 +550,7 @@ func TestInstallCopilotCLIScriptRejectsExplicitVersionBelowCopilotMinVersion(t * tempDir := t.TempDir() fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) require.NoError(t, os.WriteFile(filepath.Join(fakeBinDir, "sudo"), []byte(`#!/usr/bin/env bash if [ "${1:-}" = "chown" ]; then exit 0 @@ -561,8 +584,8 @@ func TestInstallCopilotCLIScriptUsesBoundedRetriesForReleaseDownloads(t *testing require.NoError(t, err) for _, download := range []string{ - `curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}"`, - `curl -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}"`, + `curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}"`, + `curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}"`, } { assert.Contains(t, string(script), download) } @@ -595,7 +618,7 @@ func TestInstallCopilotCLIScriptRootlessModeUsesRealScriptWithToolcacheAndNoSudo require.NoError(t, os.WriteFile(filepath.Join(toolcacheBin, "copilot"), []byte("#!/usr/bin/env bash\necho 'copilot 1.2.3'\n"), 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) sudoScript := filepath.Join(fakeBinDir, "sudo") sudoLog := filepath.Join(tempDir, "sudo.log") @@ -657,7 +680,7 @@ func TestInstallCopilotCLIScriptFallsBackToBakedInDefaultWhenCompatUnavailable(t []byte("#!/usr/bin/env bash\necho 'copilot "+defaultVersion+"'\n"), 0o755)) fakeBinDir := filepath.Join(tempDir, "fake-bin") - require.NoError(t, os.MkdirAll(fakeBinDir, 0o755)) + prepareCopilotInstallerTestBin(t, fakeBinDir) curlLog := filepath.Join(tempDir, "curl.log") sudoScript := filepath.Join(fakeBinDir, "sudo") diff --git a/pkg/constants/version_constants.go b/pkg/constants/version_constants.go index 5420565d61e..8e6adb939d2 100644 --- a/pkg/constants/version_constants.go +++ b/pkg/constants/version_constants.go @@ -349,7 +349,7 @@ const DefaultJavaVersion Version = "21" const DefaultElixirVersion Version = "1.17" // DefaultGoVersion is the default version of Go for runtime setup -const DefaultGoVersion Version = "1.26" +const DefaultGoVersion Version = "1.26.10" // DefaultHaskellVersion is the default version of GHC for runtime setup const DefaultHaskellVersion Version = "9.10" diff --git a/pkg/workflow/copilot_engine_installation.go b/pkg/workflow/copilot_engine_installation.go index 510a06aa012..e6471fd4d68 100644 --- a/pkg/workflow/copilot_engine_installation.go +++ b/pkg/workflow/copilot_engine_installation.go @@ -458,9 +458,13 @@ func generateDockerComposeInstallStep() GitHubActionStep { ` export DOCKER_CONFIG="${DOCKER_CONFIG:-$HOME/.docker}"`, ` mkdir -p "$DOCKER_CONFIG/cli-plugins"`, ` arch="$(uname -m)"`, - ` case "$arch" in x86_64|amd64) arch="x86_64" ;; aarch64|arm64) arch="aarch64" ;; *) echo "Unsupported architecture for docker compose plugin: $arch" >&2; exit 1 ;; esac`, - ` curl -fsSL "https://github.com/docker/compose/releases/download/v2.36.2/docker-compose-linux-$arch" -o "$DOCKER_CONFIG/cli-plugins/docker-compose"`, - ` chmod +x "$DOCKER_CONFIG/cli-plugins/docker-compose"`, + ` case "$arch" in x86_64|amd64) arch="x86_64"; digest="9040bd35b2cc0783ce6c5de491de7e52e24d4137dbfc5de8a524f718fc23556c" ;; aarch64|arm64) arch="aarch64"; digest="d1148609319706a57b755ff0f61d604a63a8cf57adb24c17535baa766ff14b4f" ;; *) echo "Unsupported architecture for docker compose plugin: $arch" >&2; exit 1 ;; esac`, + ` download="$(mktemp)"`, + ` trap 'rm -f "$download"' EXIT`, + ` curl --proto '=https' --proto-redir '=https' -fsSL "https://github.com/docker/compose/releases/download/v2.36.2/docker-compose-linux-$arch" -o "$download"`, + ` source "${RUNNER_TEMP}/gh-aw/actions/release_integrity.sh"`, + ` verify_release_digest "$download" "$digest"`, + ` install -m 0755 "$download" "$DOCKER_CONFIG/cli-plugins/docker-compose"`, ` docker compose version`, }) } diff --git a/pkg/workflow/firewall_version_pinning_test.go b/pkg/workflow/firewall_version_pinning_test.go index 9e477a5b90d..f31d3562661 100644 --- a/pkg/workflow/firewall_version_pinning_test.go +++ b/pkg/workflow/firewall_version_pinning_test.go @@ -3,12 +3,60 @@ package workflow import ( + "os" + "os/exec" + "path/filepath" "strings" "testing" "github.com/github/gh-aw/pkg/constants" ) +func TestBootstrapReleasePinsMatchCompilerDefaults(t *testing.T) { + t.Parallel() + helper, err := filepath.Abs(filepath.Join("..", "..", "actions", "setup", "sh", "release_integrity.sh")) + if err != nil { + t.Fatal(err) + } + for _, release := range []struct { + repository, version, manifest string + }{ + {"github/gh-aw-firewall", string(constants.DefaultFirewallVersion), "checksums.txt"}, + {"github/copilot-cli", string(constants.DefaultCopilotVersion), "SHA256SUMS.txt"}, + } { + cmd := exec.Command("bash", "-c", `source "$1"; release_artifact_sha256 "$2" "$3" "$4"`, + "--", helper, release.repository, release.version, release.manifest) + if output, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("compiler default must have a reviewed bootstrap pin: %v: %s", err, output) + } + } +} + +func TestDockerComposeBootstrapIntegrity(t *testing.T) { + t.Parallel() + step := strings.Join(generateDockerComposeInstallStep(), "\n") + for _, expected := range []string{ + `--proto '=https' --proto-redir '=https'`, + `verify_release_digest "$download" "$digest"`, + "9040bd35b2cc0783ce6c5de491de7e52e24d4137dbfc5de8a524f718fc23556c", + "d1148609319706a57b755ff0f61d604a63a8cf57adb24c17535baa766ff14b4f", + } { + if !strings.Contains(step, expected) { + t.Errorf("Compose install is missing %q", expected) + } + } + if strings.Index(step, "verify_release_digest") > strings.Index(step, "install -m 0755") { + t.Error("Compose digest verification must precede installation") + } + helper, err := os.ReadFile(filepath.Join("..", "..", "actions", "setup", "sh", "release_integrity.sh")) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(helper), "verify_release_digest()") { + t.Error("generated Compose install must use the shipped verifier") + } +} + // TestAWFInstallationStepDefaultVersion verifies that AWF installation uses the default version when not specified func TestAWFInstallationStepDefaultVersion(t *testing.T) { t.Run("uses default version when no version specified", func(t *testing.T) { diff --git a/pkg/workflow/runtime_integration_test.go b/pkg/workflow/runtime_integration_test.go index 489aad541ea..ded37b57a2e 100644 --- a/pkg/workflow/runtime_integration_test.go +++ b/pkg/workflow/runtime_integration_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/stringutil" ) @@ -360,8 +361,8 @@ Test workflow that uses Go without go.mod file. if !strings.Contains(lockStr, "actions/setup-go@") { t.Error("Expected actions/setup-go action in lock file") } - if !strings.Contains(lockStr, "go-version: '1.26'") { - t.Error("Expected go-version: '1.26' in lock file (default version)") + if !strings.Contains(lockStr, "go-version: '"+string(constants.DefaultGoVersion)+"'") { + t.Error("Expected patched default go-version in lock file") } // Ensure it does NOT use go-version-file if strings.Contains(lockStr, "go-version-file") { diff --git a/pkg/workflow/runtime_setup_test.go b/pkg/workflow/runtime_setup_test.go index 937281a9ac5..15161df04fb 100644 --- a/pkg/workflow/runtime_setup_test.go +++ b/pkg/workflow/runtime_setup_test.go @@ -460,7 +460,7 @@ func TestGenerateRuntimeSetupSteps(t *testing.T) { checkContent: []string{ "Setup Go", "actions/setup-go@", - "go-version: '1.26'", + "go-version: '1.26.10'", "cache: false", "Capture GOROOT for AWF chroot mode", }, diff --git a/pkg/workflow/tls_trust_conformance_test.go b/pkg/workflow/tls_trust_conformance_test.go new file mode 100644 index 00000000000..38a2b3de9ef --- /dev/null +++ b/pkg/workflow/tls_trust_conformance_test.go @@ -0,0 +1,174 @@ +//go:build !integration + +package workflow + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "math/big" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "strings" + "sync/atomic" + "testing" + "time" +) + +func TestTransportTrustBootstrapRedirectPolicy(t *testing.T) { + t.Parallel() + curl, err := exec.LookPath("curl") + if err != nil { + t.Skip("curl is required for the bootstrap redirect conformance test") + } + var insecureRequests atomic.Int32 + insecure := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + insecureRequests.Add(1) + _, _ = w.Write([]byte("untrusted")) + })) + defer insecure.Close() + secure := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/redirect" { + http.Redirect(w, r, insecure.URL, http.StatusFound) + return + } + _, _ = w.Write([]byte("verified")) + })) + defer secure.Close() + caFile := filepath.Join(t.TempDir(), "test-ca.pem") + caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: secure.Certificate().Raw}) + if err := os.WriteFile(caFile, caPEM, 0o600); err != nil { + t.Fatal(err) + } + options := []string{"--proto", "=https", "--proto-redir", "=https", "-fsSL", "--max-time", "5", "--noproxy", "*", "--cacert", caFile} + control, err := exec.Command(curl, append(options, secure.URL)...).CombinedOutput() + if err != nil || string(control) != "verified" { + t.Fatalf("valid HTTPS control failed: %v: %s", err, control) + } + output, err := exec.Command(curl, append(options, secure.URL+"/redirect")...).CombinedOutput() + if err == nil || !strings.Contains(strings.ToLower(string(output)), "disabled") || insecureRequests.Load() != 0 { + t.Fatalf("HTTP redirect must fail before contacting its destination: %v; requests=%d; output=%s", err, insecureRequests.Load(), output) + } +} + +func TestTransportTrustRejectsCompromisedCAKeysAcrossCrossSigns(t *testing.T) { + t.Parallel() + var first *x509.Certificate + for i, encoded := range compromisedCACertificates { + block, rest := pem.Decode([]byte(encoded)) + if block == nil || len(rest) != 0 { + t.Fatalf("invalid public certificate fixture %d", i) + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal(err) + } + if first == nil { + first = cert + } else if !bytes.Equal(first.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) || bytes.Equal(first.Raw, cert.Raw) { + t.Fatal("fixtures must represent the same compromised key under different issuers") + } + // Explicit trust avoids relying on a particular OS root bundle. Even an + // explicitly trusted cross-sign must not bypass Go's key-level distrust. + roots := x509.NewCertPool() + roots.AddCert(cert) + chains, err := cert.Verify(x509.VerifyOptions{ + Roots: roots, + CurrentTime: time.Date(2026, 10, 20, 0, 0, 0, 0, time.UTC), + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + }) + var invalid x509.CertificateInvalidError + if !errors.As(err, &invalid) || invalid.Reason != x509.NoValidChains || len(chains) != 0 { + t.Fatalf("compromised certificate %d must fail key-level distrust, got chains=%d err=%v", i, len(chains), err) + } + } +} + +func TestTransportTrustAcceptsUnaffectedCA(t *testing.T) { + t.Parallel() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + now := time.Now() + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "unaffected test CA"}, + NotBefore: now.Add(-time.Hour), + NotAfter: now.Add(time.Hour), + IsCA: true, + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageCertSign, + } + der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + if err != nil { + t.Fatal(err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + t.Fatal(err) + } + roots := x509.NewCertPool() + roots.AddCert(cert) + if _, err := cert.Verify(x509.VerifyOptions{Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}}); err != nil { + t.Fatalf("valid control chain must still verify: %v", err) + } +} + +// Public certificates only, from +// https://emudhra.we-have-a-problem.com/impacted-intermediates.json (2026-10-20). +// These are the original and Sectigo cross-sign of EM DV TLS CA - G3B. +var compromisedCACertificates = []string{ + `-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIQAKSu5njyM2K0wORgt3LyFDAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTU1 +NDA4WhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11 +ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE557ZwEqXc8RzXlERHahawr1eZKs3S4DhLLwgOOweTw47gS/J +hZ4eZ6+wTi+LyX90hbplqpEe6KTkW2LVzAPVa+Z4s8OMtNyTfKJGDiOEU8iSk10p +1E+R4VYGtjzO2rRoo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFOIeL+ccGTkOVUtiwSAkVzUkiiUUMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYi5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQi5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIwLmsGXi6oFMJ2klkcEMXMug6gvxQWZWkewiDWVzQyl2L6z6DuQfPO +Ibh1grvdULDCAjEAxZvLlxUavG69J6wnSjC8XBbqNIQsJ6CE2QdNOe84Ptxb6VG8 +gt2TlfJbHii+KNvK +-----END CERTIFICATE-----`, + `-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIRAKAk/fS6Zlx4XOSgIEg4WLcwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBEViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATnntnASpdzxHNeUREdqFrCvV5kqzdLgOEsvCA47B5PDjuBL8mFnh5nr7BO +L4vJf3SFumWqkR7opORbYtXMA9Vr5nizw4y03JN8okYOI4RTyJKTXSnUT5HhVga2 +PM7atGijggE1MIIBMTAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQU4h4v5xwZOQ5VS2LBICRXNSSKJRQwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDEGA1UdIAQqMCgwCAYGZ4EMAQIBMA0GCysGAQQBg44hAQABMA0GCysGAQQBg44h +AQJkMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB +QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQBzQ1IoqpdotM6gFrR4Q3YrKifOLeaj5XXunldpVE/vZn2TxC2y0s8oYiLR5ICE +wqVXSB7N6BM1Mp+Nif7Jbhb2IswAfSR4Iik8+B0iRmi7lYoNlycCgHjmsjOmZaxM +QkPmjXAJbG5f4V41OsvJZmS7lI3r0IuvEDVX3GAZ13on6IImSs9ybNpHQ7NCqkMQ +7wH4uAO1dtpFycgJSdHaCOhHjrox2BTYGLdpUrYwY3EOAjlZ8UGgsxs7mG+xquMl +bN1LFURwfnkVMP3T/DBIZ/ys2+lNFyxK3FlArrV4/000uTnVB1lO8oGORYdXoT5V +GUyRCEIDhF5TBUuKF4E3kf1h +-----END CERTIFICATE-----`, +} diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 4b0ea9e86a0..de493bfd5b8 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -29,8 +29,12 @@ platforms=( echo "Building binaries with version: $VERSION" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_TOOLCHAIN="$(bash "${SCRIPT_DIR}/check-go-toolchain.sh")" + # Create dist directory if it doesn't exist mkdir -p dist +printf '%s\n' "$BUILD_TOOLCHAIN" > dist/build-toolchain.txt IFS=$'\n' read -d '' -r -a supported_platforms < <(go tool dist list) || true @@ -57,7 +61,12 @@ for p in "${platforms[@]}"; do -ldflags="-s -w -X main.version=${VERSION} -X main.isRelease=true" \ -o "dist/${p}${ext}" \ ./cmd/gh-aw - + + binary_toolchain="$(go version -m "dist/${p}${ext}" | head -1)" + if [[ "$binary_toolchain" != *": ${BUILD_TOOLCHAIN}" ]]; then + echo "ERROR: Unexpected build toolchain for ${p}: ${binary_toolchain}" >&2 + exit 1 + fi done # Build WebAssembly binary diff --git a/scripts/check-go-toolchain.sh b/scripts/check-go-toolchain.sh new file mode 100644 index 00000000000..ed2f5e5a31e --- /dev/null +++ b/scripts/check-go-toolchain.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +# Check the actual compiler, not just the module directive or setup-go request. +version="$(go env GOVERSION)" +if [[ ! "$version" =~ ^go1\.([0-9]+)\.([0-9]+)$ ]]; then + echo "ERROR: Release builds require a stable patched Go toolchain; found ${version}" >&2 + exit 1 +fi +minor="${BASH_REMATCH[1]}" +patch="${BASH_REMATCH[2]}" +if (( minor < 26 || (minor == 26 && patch < 10) || (minor == 27 && patch < 3) )); then + echo "ERROR: ${version} lacks the required CA distrust fix (CVE-2026-111943); use Go 1.26.10, 1.27.3, or a newer supported release" >&2 + exit 1 +fi +printf '%s\n' "$version" diff --git a/scripts/check-go-toolchain_test.sh b/scripts/check-go-toolchain_test.sh new file mode 100644 index 00000000000..6609a7956a9 --- /dev/null +++ b/scripts/check-go-toolchain_test.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TEMP_DIR"' EXIT +cat >"$TEMP_DIR/go" <<'EOF' +#!/usr/bin/env bash +printf '%s\n' "$TEST_GO_VERSION" +EOF +chmod +x "$TEMP_DIR/go" +for version in go1.26.10 go1.26.11 go1.27.3 go1.27.4 go1.28.0; do + TEST_GO_VERSION="$version" PATH="$TEMP_DIR:$PATH" bash "$SCRIPT_DIR/check-go-toolchain.sh" >/dev/null +done +for version in go1.25.20 go1.26.9 go1.27.0 go1.27.1 go1.27.2 go1.28rc1 devel; do + if TEST_GO_VERSION="$version" PATH="$TEMP_DIR:$PATH" bash "$SCRIPT_DIR/check-go-toolchain.sh" >"$TEMP_DIR/output" 2>&1; then + echo "FAIL: accepted unpatched or nonstable toolchain $version" >&2 + exit 1 + fi + grep -q 'ERROR:' "$TEMP_DIR/output" +done +echo "PASS: release toolchain floors and stable-version checks" diff --git a/scripts/update-threat-detect-pins.sh b/scripts/update-threat-detect-pins.sh index f42edfb7ce5..f749834e6c0 100755 --- a/scripts/update-threat-detect-pins.sh +++ b/scripts/update-threat-detect-pins.sh @@ -67,7 +67,7 @@ trap cleanup EXIT RELEASE_URL="${RELEASE_BASE_URL%/}/${VERSION}" MANIFEST="$TEMP_DIR/checksums.txt" echo "Downloading ${RELEASE_URL}/checksums.txt" -curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors \ +curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-all-errors \ -o "$MANIFEST" "${RELEASE_URL}/checksums.txt" declare -A EXPECTED_DIGESTS=() @@ -114,7 +114,7 @@ fi for asset in "${ASSETS[@]}"; do echo "Downloading and verifying $asset" - curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors \ + curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-all-errors \ -o "$TEMP_DIR/$asset" "${RELEASE_URL}/${asset}" actual_digest="$(sha256_hash "$TEMP_DIR/$asset")" if [ "$actual_digest" != "${EXPECTED_DIGESTS[$asset]}" ]; then diff --git a/specs/compiler-threat-detection-changelog.md b/specs/compiler-threat-detection-changelog.md index 004ef8a7e51..f303b78818b 100644 --- a/specs/compiler-threat-detection-changelog.md +++ b/specs/compiler-threat-detection-changelog.md @@ -16,6 +16,7 @@ This changelog records the version history and the dated mapping audits for `spe | Version | Change | |---|---| +| 1.0.64 | Adds the compromised-intermediate-CA threat and maps bootstrap/runtime controls to security architecture §6.8: independently authenticated AWF/Copilot checksum manifests, compiler-pinned Compose bytes, authenticated apt signing key, HTTPS-only redirects, actual Go release-toolchain verification and bounded runtime inventory. Preserves CTR-011 destination authorization as a separate boundary and records broader download enforcement as a candidate. Unreviewed release downloads fail closed; no new threat identifier or lock-file schema change. | | 1.0.63 | Audit-only clarification of the Copilot SDK runtime boundary: explicit staged-only skill registration with ambient discovery disabled, hard rejection of file and symlink roots, and unchanged permission-denial counting and termination. Separates prompt recovery guidance from authorization and preserves CTR-023 engine-capability checks and CTR-028/030 generated provenance requirements. No new threat identifier or lock-file schema change. | | 1.0.62 | Added authored `pull-request-target-checkout` (CTR-008 subset) and `prompt-expression` (CTR-010 subset) rules, bringing the AST registry to 23 rules. Shared existing base-ref and literal-pair trust predicates with compiler checkout validation and reused the Actions expression visitor for prompt checks. Preserved authored strict/non-strict checkout policy, import uncertainty, exact allowlist matching, value-free prompt diagnostics, and native Actions evidence boundaries. Prompt checks reject multiline/malformed interpolations and blocked property traversal without claiming the full context allowlist. Added registry, CLI, compiler-policy, cancellation, and repository-workflow regressions. No new threat identifier or lock-file schema change. | | 1.0.61 | Added known compiler threat IDs to every built-in AST rule, propagated registry-owned references through text/JSON diagnostics and rule listings, and moved detailed lint reference content out of the CLI page. Unmapped extension rules can omit the reference. Checks URL-filter SSL prerequisites in compiler-accepted non-strict `sandbox.agent.args`; legacy `network.firewall` blocks require migration rather than an invalid SSL-setting remedy. Added mapping, output, and source-to-compiler regressions without changing the compiler schema. No new threat identifier or lock-file schema change. | diff --git a/specs/compiler-threat-detection-spec.md b/specs/compiler-threat-detection-spec.md index b0d53966660..569b178e841 100644 --- a/specs/compiler-threat-detection-spec.md +++ b/specs/compiler-threat-detection-spec.md @@ -7,7 +7,7 @@ sidebar: # GitHub Actions Compiler Threat Detection Specification -**Version**: 1.0.63 +**Version**: 1.0.64 **Status**: Candidate Recommendation **Latest Version**: https://github.com/github/gh-aw/blob/main/specs/compiler-threat-detection-spec.md **Editors**: GitHub Next (GitHub, Inc.) @@ -32,6 +32,7 @@ Each version maps to the minimum compatible binary. A version change MUST update | Versions | Minimum gh-aw | Compatibility | |---|---:|---| +| `1.0.64` | Unreleased | Separates compromised-CA transport authentication from CTR-011 destination authorization. Maps trusted release digests, HTTPS-only bootstrap redirects, patched release toolchains and runtime inventory to security architecture §6.8. Unreviewed AWF/Copilot release downloads fail closed; no new `CTR-*` identifier or lock-file schema change. | | `1.0.63` | Unreleased | Audit-only; distinguishes staged Copilot SDK skill registration and runtime permission-denial enforcement from compiler controls. Preserves CTR-023 engine-capability validation and CTR-028/030 configuration provenance. No new threat identifier, permission grant, or lock-file schema change. | | `1.0.62` | Unreleased | Adds authored AST checks for `pull_request_target` checkout trust (CTR-008 subset) and multiline, malformed, or blocked-property prompt expressions (CTR-010 subset). Shares checkout trust predicates with the compiler and expression parsing with secret-flow analysis. Preserves unresolved-import uncertainty and native Actions evidence boundaries. No new threat identifier or lock-file schema change. | | `1.0.61` | Unreleased | Exposes known compiler threat IDs in AST rule metadata, text/JSON diagnostics, rule listings, and the standalone linter reference. Checks URL-filter SSL prerequisites in accepted non-strict `sandbox.agent.args`; reports schema-rejected legacy `network.firewall` as requiring migration rather than recommending an invalid remedy. No new threat identifier or lock-file schema change. | @@ -75,6 +76,10 @@ Each version maps to the minimum compatible binary. A version change MUST update Generated workflows run with elevated permissions and consume untrusted content (issues, PRs, comments, external tool output). The catalog in Section 5 groups the threats a conforming compiler MUST detect into five classes: unauthorized privilege or scope expansion, unsafe or bypassed sandboxing, injection (template, shell, or subprocess argument), unsafe output and supply-chain routes (including forged execution attribution), and compile-time drift between manifests, mappings, and the rules that reference them. Each class maps to one or more `CTR-*` rules with a stable trigger and compiler action. +An adversary may also intercept network traffic while possessing a formerly trusted intermediate CA private key, as in the [October 2026 eMudhra compromise](https://emudhra.we-have-a-problem.com). Such an adversary can impersonate an allowlisted HTTPS service to an unpatched client and replace both an executable and an unsigned checksum served beside it. CTR-011 authorizes destinations; it does not authenticate their operators or attest client distrust updates. CTR-002 action pins and CTR-016 manifest checks likewise do not establish remote TLS identity. + +Compiler-controlled bootstrap code uses independently provisioned release digests and HTTPS-only redirects as mapped in [security architecture §6.8](security-architecture-spec.md#68-transport-trust-and-bootstrap-integrity). The trusted setup action authenticates AWF and Copilot checksum manifests before consuming asset digests; threat-detect uses compiler-supplied asset digests, and Docker Compose uses compiler-supplied per-architecture digests. Release builds enforce the actual Go toolchain floor independently of `go.mod`. These are bootstrap/runtime controls, not a new `CTR-*` rejection of workflow network declarations. Broader executable-download enforcement remains a candidate under §5.3 until all engine/package-manager paths have independent authentication and deployment evidence. Compilation cannot attest arbitrary runner, proxy, MCP, or container trust stores; runtime inventory is diagnostic evidence, not conformance proof. + Runtime trust boundaries inside the agent workspace are out of scope for this specification. In particular, the safe-outputs repository checkout discovery path (`actions/setup/js/find_repo_checkout.cjs`) resolves an `owner/repo` target from agent-writable workspace state; its normative controls — manifest precedence, workspace confinement, per-invocation `safe.directory` scoping, read-only discovery, remote host constraint, and deferred durable trust — are specified as Threat T7 and requirements RCR1–RCR7 in the Safe Outputs MCP Gateway Specification and as §3.5 of the Checkout Behavior Specification. A conforming compiler is not required to detect this class, because the compiler emits no generated-workflow construct that selects the checkout directory. PR-branch checkout authorization in `actions/setup/js/checkout_pr_branch.cjs` is also a runtime trust control, not a compiler-detectable threat. The same-repository bot exception covers `pull_request` and `pull_request_target` `opened`, `synchronize`, `ready_for_review`, and `reopened` events with matching bot sender and actor plus equal positive runtime, PR head, and PR base repository IDs. For `issue_comment`, `pull_request_review_comment`, and `pull_request_review`, an explicitly `on.bots`-allow-listed App may bypass the collaborator check only when `sender.type` is `Bot`, the sender and matching comment or review author logins match exactly, the sender is canonically equivalent to the runtime actor (App slug or `[bot]` form), the runtime repository ID is positive, and the fetched PR head and base repository IDs both match it. Forks, unlisted bots, unverifiable or mismatched identities, and other actors retain the collaborator-permission check. The normative requirements are in the Security Architecture Specification, RS-05a; this control does not add a `CTR-*` rule. diff --git a/specs/security-architecture-spec.md b/specs/security-architecture-spec.md index 468f26bf625..f68056d1018 100644 --- a/specs/security-architecture-spec.md +++ b/specs/security-architecture-spec.md @@ -7,7 +7,7 @@ sidebar: # GitHub Agentic Workflows Security Architecture Specification -**Version**: 1.1.3
+**Version**: 1.1.4
**Status**: Candidate Recommendation **Latest Version**: https://github.com/github/gh-aw/blob/main/specs/security-architecture-spec.md **Editors**: GitHub Next (GitHub, Inc.) @@ -78,7 +78,7 @@ This specification does NOT cover: - Application-level security for workflows (responsibility of workflow authors) - GitHub Actions platform security (covered by GitHub documentation) - Operational security procedures for managing workflows -- Incident response or security monitoring procedures +- General incident response or security monitoring procedures (the transport-trust update procedure in §6.8 is included) ### 1.3 Design Goals @@ -229,6 +229,7 @@ The security architecture protects against: 4. **Privilege Escalation**: Unauthorized elevation of permissions or capabilities 5. **Supply Chain Attacks**: Compromised dependencies or actions 6. **Resource Exhaustion**: Denial of service through infinite loops or resource consumption +7. **Compromised Certificate Authorities**: An attacker with network interception capability and a formerly trusted intermediate CA key can impersonate allowed HTTPS destinations to clients that have not applied distrust updates. Domain allowlisting, browser updates, and unsigned checksums fetched alongside artifacts do not independently prevent this attack. **Threat Model Clarification**: While the input sanitization layer prevents template injection through GitHub event context, AI agents may still access untrusted data at runtime through MCP tools. For example, the GitHub MCP server can return issue titles, PR bodies, and comments that were not pre-sanitized by the activation job. This is an accepted architectural tradeoff that enables dynamic workflows while relying on: - **Threat detection**: Analyzes agent output for malicious behavior before execution @@ -538,6 +539,42 @@ network: - URIs from non-allowed domains MUST be replaced with `(redacted)` - GitHub domains SHOULD be allowed by default +### 6.8 Transport Trust and Bootstrap Integrity + +Destination authorization, endpoint authentication, and artifact authentication are separate boundaries. Ordinary hostname/chain validation is necessary but insufficient when the client still trusts a compromised CA. SSL interception adds another verifier: updating the client-side proxy CA does not update the proxy's upstream trust store. + +| Requirement | Contract and evidence | +|---|---| +| **TT-01 TLS policy** | Bootstrap curl downloads MUST restrict both initial and redirected URLs to HTTPS and MUST retain hostname/chain validation. No insecure certificate override or HTTP retry is permitted. `install_awf_binary.sh`, `install_copilot_cli.sh`, `install_threat_detect_binary.sh`, the GitHub CLI key installer, Cloud Hypervisor setup downloads, and the generated Compose installer implement this policy. | +| **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest shipped in the trusted setup action's `release_integrity.sh` before consuming it. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests; the GitHub CLI apt signing key is authenticated before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | +| **TT-03 Toolchain evidence** | Release builds MUST reject stable Go toolchains older than 1.26.10 on the 1.26 branch or 1.27.3 on the 1.27 branch, unsupported older branches, and development/prerelease toolchains. `scripts/check-go-toolchain.sh` checks the actual compiler; `scripts/build-release.sh` inspects each native binary's embedded toolchain and records `dist/build-toolchain.txt`. The module's preferred toolchain and default generated Go setup are patched; user-selected Go runtimes remain author-controlled and are not attested by compilation. | +| **TT-04 Runtime evidence** | Setup MUST log a bounded inventory of available host TLS consumers without credentials, environment dumps, or authenticated URLs. Missing inspection tools and uninspected container/proxy trust stores MUST be identified rather than reported as compliant. `runtime_trust_inventory.sh` supplies host versions; image operators retain responsibility for image contents and actual trust-store selection. | + +The trusted bootstrap baseline includes the setup action and its digest table, compiler, GitHub Actions control plane, and runner/toolcache provisioning. It excludes attacker-controlled network responses. A preinstalled Copilot CLI from a trusted runner toolcache retains the existing cache policy; cache contents are not newly authenticated by download checks. An arbitrary runner binary, compromised setup action, or malicious workflow author is not repaired by a digest table. + +#### Runtime trust inventory and ownership + +| Consumer | Trust authority to inspect | Owner and required update evidence | +|---|---|---| +| gh-aw and gh Go binaries | Embedded Go `crypto/x509` plus platform verifier/root store | Release maintainers record the actual build toolchain; runner operators inspect separately provisioned `gh`. A newer `go` executable does not patch existing binaries. | +| Host Git/curl and apt | curl TLS backend, OS CA/blocklist packages, apt signing keys | Runner-image operators update and rebuild images; TLS bootstrap of signing keys also requires independent key authentication. | +| Node and engine CLIs | Node/OpenSSL version, bundled/system CAs and vendor distrust implementation | Runtime vendors and runner operators provide patched-version/distrust evidence. Do not invent a Node patch floor while its vendor fix is pending. | +| Python clients | OpenSSL/system CAs or requests/certifi bundle actually selected by the client | Runtime-image operators update the selected bundle; certifi 2026.10.19 addresses this incident. Merely installing certifi does not change every client's CA source. | +| AWF Squid/API/CLI proxies | Upstream TLS verifier inside each digest-pinned image | AWF maintainers rebuild affected images; gh-aw maintainers review and refresh digests. Pinning freezes bytes, including an outdated trust store. | +| MCP servers and guest/container engines | Each image or guest's TLS implementation, custom CA configuration and dependency bundles | Image/guest owners rebuild and attest each consumer separately. Updating only the host or browser is insufficient. | + +#### Emergency distrust procedure + +1. Confirm affected keys, cross-signs and vendor guidance from the [incident report](https://emudhra.we-have-a-problem.com) and [Go advisory](https://groups.google.com/g/golang-announce/c/66pojueaQek). Track key/SPKI identity across alternative chains, not only one certificate fingerprint. Do not download or use compromised private keys. +2. Inventory the consumers above and their deployed versions/digests. Treat unavailable vendor evidence as unknown, not protected. Do not rely on universal OCSP/CRL consumption or a successful HTTPS probe. +3. Update supported trust stores and runtimes, rebuild distributed Go binaries and affected images, and review new pins from an independently authenticated release process. Never acquire an emergency trusted pin solely from the same potentially intercepted download channel. Reject rollback to known-vulnerable releases; do not change historical run results. +4. Exercise offline distrust tests for affected keys and their cross-signs, artifact-plus-checksum replacement, rejection before execution, supported architecture selection, and HTTPS downgrade rejection. Keep a valid-chain control to distinguish a deliberate distrust failure from an otherwise broken verifier. +5. Publish new compiler/setup releases and refreshed image digests through the normal release process. Record build-toolchain evidence and remaining unpatched consumers. Do not disable certificate validation or weaken the sandbox to restore availability; temporarily suspend affected execution where no trustworthy client is available. + +#### Conformance evidence and limits + +`release_integrity_test.sh`, `install_awf_binary_test.sh`, `install_threat_detect_binary_test.sh`, `scripts/check-go-toolchain_test.sh`, and the focused Go installer/compiler tests exercise these controls. `TestTransportTrustRejectsCompromisedCAKeysAcrossCrossSigns` checks a representative original/cross-signed public-certificate pair against Go's native key-level distrust using explicit trust anchors; `TestTransportTrustAcceptsUnaffectedCA` is the valid-chain control. These are offline verifier tests, not live TLS or comprehensive vendor/store attestations. `TestTransportTrustBootstrapRedirectPolicy` uses a local trusted HTTPS server to verify that an HTTP redirect is refused before its destination is contacted. Broader package-manager downloads and upstream images need their own signature/lockfile and vendor deployment evidence; this change does not attest or patch upstream images. A compiler-wide rejection rule for unauthenticated downloads is a candidate, not an active `CTR-*` rule, until implementation and deployment evidence satisfy the compiler specification's candidacy process. + --- ## 7. Permission Management Layer @@ -2053,6 +2090,14 @@ roles: [admin, maintainer] # Restrict to trusted roles ## Change Log +### Version 1.1.4 (Transport Trust and Bootstrap Integrity) + +**Published**: October 20, 2026 + +- Adds the compromised-intermediate-CA threat and TT-01 through TT-04 bootstrap, toolchain and inventory contracts, separate from domain authorization. +- Authenticates AWF/Copilot checksum manifests independently, pins Compose and apt signing-key bytes, and rejects HTTPS bootstrap downgrade. +- Documents per-consumer trust ownership, emergency distrust updates and remaining upstream/package-manager coverage limitations. + ### Version 1.1.3 (RS-05a Same-Repository Bot PR Event Checkout) **Published**: October 16, 2026 From 7d25fa5d826dfb3c40d871e2e6d470d8ba9d80e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:27:44 +0000 Subject: [PATCH 2/8] docs(adr): add draft ADR-70549 for bundled release-pin bootstrap integrity --- ...rap-downloads-with-bundled-release-pins.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md diff --git a/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md new file mode 100644 index 00000000000..16d3a12aa81 --- /dev/null +++ b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md @@ -0,0 +1,52 @@ +# ADR-70549: Authenticate Bootstrap Downloads with Release-Bound Digests in the Setup Action + +**Date**: 2026-10-20 +**Status**: Draft +**Deciders**: pelikhan, gh-aw maintainers [TODO: verify] + +--- + +### Context + +The setup action's bootstrap installers (`install_awf_binary.sh`, `install_copilot_cli.sh`, the generated Docker Compose step, and `install_gh_cli.sh`) download executables over HTTPS and check them against a checksum manifest (`checksums.txt` / `SHA256SUMS.txt`) fetched from the same release over the same channel. If a certificate authority trusted by the runner is compromised, an attacker who can intercept traffic can impersonate an allowed HTTPS host. That attacker can then replace both the artifact and its checksum file, so the checksum check provides no independent authentication. Domain allowlisting decides *where* a client may connect. It does not prove *who* answered or *what* bytes arrived. The compiler and generated Go runtime also need a toolchain that implements the relevant upstream distrust behavior. + +### Decision + +We will authenticate bootstrap artifacts against SHA256 digests bound to specific releases and shipped inside the trusted setup action, not just against network-served checksums. A new `actions/setup/sh/release_integrity.sh` holds a reviewed pin table with entries of the form `(repository, version, asset, sha256)`. AWF and Copilot CLI installers must verify their downloaded checksum manifest against this table before using it. Docker Compose bytes and the GitHub CLI apt signing key are pinned directly. If a release version has no pin (including `latest`), the install fails closed. An AWF bundle that fails verification no longer falls back to the standalone binary. All bootstrap `curl` calls are limited to HTTPS for both the initial request and any redirects (`--proto '=https' --proto-redir '=https'`). Release builds must use Go ≥ 1.26.10 or ≥ 1.27.3, and each native binary's embedded toolchain is recorded. Together, these steps separate destination authorization, transport authentication, and artifact integrity as distinct security boundaries. + +### Alternatives Considered + +#### Alternative 1: Rely on TLS validation plus network-served checksums (status quo) + +Keep downloading `checksums.txt` / `SHA256SUMS.txt` alongside the artifact and rely on ordinary hostname and chain validation. This option needs no maintenance and supports any upstream version, including `latest`. We rejected it because a compromised CA defeats both checks together. An attacker who can impersonate the host can serve a matching artifact and checksum pair. + +#### Alternative 2: Verify upstream cryptographic signatures or attestations (e.g., Sigstore / GitHub artifact attestations) + +Verify publisher-signed provenance instead of maintaining a digest table. This would remove the manual pin updates and scale to new releases automatically. We did not choose it here because it needs signature or attestation support from every upstream (AWF, Copilot CLI, Docker Compose), plus verifier tooling and trust roots on the runner. Those trust roots would themselves need bootstrapping. The PR does not show that these prerequisites are in place. [TODO: verify whether this was evaluated] + +#### Alternative 3: Pin only the final artifact digests in the compiler (per architecture) + +Embed per-architecture artifact digests in compiled lock files, as threat-detect and Compose already do. This avoids trusting the manifest at all. For AWF and Copilot, which ship many platform and libc variants, pinning one manifest digest per release covers every variant with a single reviewed entry. For that reason, manifest pinning was chosen for those two, and direct pins were kept where they already existed. + +### Consequences + +#### Positive +- Replacing both an artifact and its checksum manifest over an intercepted TLS session no longer bypasses verification. Integrity is anchored in the trusted setup action. +- HTTPS-only redirects block downgrade-to-HTTP attacks during bootstrap. Tests use a real local HTTPS-to-HTTP redirect to confirm the rejection. +- Fail-closed behavior, with no AWF binary fallback after a bundle integrity failure, removes a silent path around verification. +- Release toolchain checks and recorded evidence make it auditable which Go `crypto/x509` behavior each shipped binary contains. + +#### Negative +- **Breaking change**: AWF/Copilot versions without a pin and uncached `latest` downloads now fail. Users must pin to a reviewed release or wait for a setup-action release that adds new pins. +- Every new upstream release needs a maintainer-reviewed pin update, obtained through an independently authenticated channel, in `release_integrity.sh`. This adds release friction and a new maintenance burden. +- The trust anchor moves to the setup action and the compiler. A compromised setup action, runner image, or toolcache is out of scope and is not mitigated. +- Requiring a newer Go version (module go directive 1.26.10 and a preferred 1.27.3 toolchain) may break contributors or build environments using older toolchains. + +#### Neutral +- Container images, MCP servers, Node/Python clients, and proxy trust stores are not patched or attested. `runtime_trust_inventory.sh` only logs diagnostic version evidence. +- The security architecture spec gains §6.8, "Transport Trust and Bootstrap Integrity" (TT-01..TT-04), and an emergency distrust procedure. Broader enforcement for package-manager downloads is recorded as a candidate rule, not an active `CTR-*` rule. +- Existing threat-detect asset pins, Cloud Hypervisor/NVX attestation checks, and trusted runner toolcache selection are unchanged. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* From 61979b525476a7e2973d42cbcb7f9e79f2c3ebc7 Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 09:44:53 -0700 Subject: [PATCH 3/8] Allow trusted release checksum pins in workflow frontmatter Add engine.checksums-sha256 and sandbox.agent.checksums-sha256 with literal version binding, propagate pins through detection and eval installers, and verify explicitly pinned Copilot host and musl downloads without toolcache bypasses. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .changeset/compiler-transport-trust.md | 2 +- CHANGELOG.md | 18 +- actions/setup/sh/install_awf_binary.sh | 31 +++- actions/setup/sh/install_copilot_cli.sh | 58 ++++-- actions/setup/sh/release_integrity.sh | 15 +- actions/setup/sh/release_integrity_test.sh | 61 ++++++- docs/src/content/docs/reference/engines.md | 31 +++- docs/src/content/docs/reference/sandbox.md | 17 ++ pkg/parser/schema_release_integrity_test.go | 34 ++++ pkg/parser/schemas/main_workflow_schema.json | 10 ++ pkg/workflow/compiler_validators.go | 4 + pkg/workflow/copilot_engine_installation.go | 9 +- pkg/workflow/copilot_installer.go | 15 +- pkg/workflow/engine.go | 4 + pkg/workflow/evals_steps.go | 3 +- .../frontmatter_extraction_security.go | 4 + pkg/workflow/release_integrity_validation.go | 63 +++++++ .../release_integrity_validation_test.go | 165 ++++++++++++++++++ pkg/workflow/sandbox.go | 39 +++-- pkg/workflow/threat_detection_external.go | 14 +- pkg/workflow/threat_detection_helpers.go | 1 + specs/compiler-threat-detection-changelog.md | 2 +- specs/compiler-threat-detection-spec.md | 4 +- specs/security-architecture-spec.md | 6 +- 24 files changed, 533 insertions(+), 77 deletions(-) create mode 100644 pkg/parser/schema_release_integrity_test.go create mode 100644 pkg/workflow/release_integrity_validation.go create mode 100644 pkg/workflow/release_integrity_validation_test.go diff --git a/.changeset/compiler-transport-trust.md b/.changeset/compiler-transport-trust.md index 8e2d05fc53a..47688a6ec17 100644 --- a/.changeset/compiler-transport-trust.md +++ b/.changeset/compiler-transport-trust.md @@ -4,4 +4,4 @@ Harden executable bootstrap against compromised certificate authorities with trusted release-manifest digests, HTTPS-only redirects, pinned Compose and apt-key bytes, patched Go release toolchains, and bounded runtime TLS inventory. -**Breaking change:** Unreviewed AWF/Copilot versions and `latest` downloads fail closed. Use a reviewed pinned release or add independently authenticated, release-bound manifest digests to the trusted setup action's `release_integrity.sh` and publish that action through the normal release process. Trusted runner toolcache selection remains unchanged. See security architecture §6.8 for emergency distrust and per-runtime ownership. +**Breaking change:** Unreviewed AWF/Copilot downloads without a supplied pin and `latest` downloads fail closed. Use a reviewed built-in release or provide an explicit version with `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) or `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`). These literal frontmatter pins propagate to matching detection/eval installations and must come from an independently trusted channel. Explicit Copilot pins bypass the toolcache; cache behavior without a supplied pin is unchanged. Updating the trusted setup action's built-in digest table remains supported. See security architecture §6.8 for emergency distrust and per-runtime ownership. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f4af769eed..97cb8ff7a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,17 @@ All notable changes to this project will be documented in this file. ### Breaking changes AWF and downloaded Copilot CLI releases now require a reviewed checksum-manifest -digest bundled in the trusted setup action. Unreviewed custom versions and -`latest` downloads fail closed instead of trusting a checksum from the same -HTTPS origin. Use a reviewed pinned release (AWF v0.28.62 or Copilot CLI -v1.0.98; the legacy Copilot v1.0.21 manifest remains available), or add the -release-bound digest to `actions/setup/sh/release_integrity.sh` through a trusted -setup-action release. Update/recompile workflows to consume that action. -Trusted runner toolcache selection is unchanged. Go release builds require +digest bundled in the trusted setup action or supplied as literal +`sandbox.agent.checksums-sha256` (AWF) / `engine.checksums-sha256` (Copilot) +frontmatter alongside an explicit release version. These values pin +`checksums.txt` / `SHA256SUMS.txt`, not individual executables. Unreviewed +downloads without a supplied pin and `latest` downloads fail closed instead of +trusting a checksum from the same HTTPS origin. Built-in pins cover AWF v0.28.62, +Copilot CLI v1.0.98, and the legacy Copilot v1.0.21 manifest. Obtain other +manifest digests through an independently trusted channel and update/recompile +workflows to consume the new setup action. Explicit Copilot pins bypass +toolcache selection; without a supplied pin, trusted toolcache behavior is +unchanged. Go release builds require 1.26.10, 1.27.3, or a newer supported stable toolchain. Logs episode rollups now require a recorded nonempty `context.episode_id`. diff --git a/actions/setup/sh/install_awf_binary.sh b/actions/setup/sh/install_awf_binary.sh index 04d2c1da329..c640127515d 100755 --- a/actions/setup/sh/install_awf_binary.sh +++ b/actions/setup/sh/install_awf_binary.sh @@ -2,13 +2,14 @@ set +o histexpand # Install AWF (Agentic Workflow Firewall) with SHA256 checksum verification -# Usage: install_awf_binary.sh VERSION +# Usage: install_awf_binary.sh VERSION [--checksums-sha256 DIGEST] [--rootless] # # This script downloads the AWF bundle or binary from GitHub releases and verifies # its SHA256 checksum before installation to protect against supply chain attacks. # # Arguments: # VERSION - AWF version to install (e.g., v0.25.10) +# --checksums-sha256 - Independently trusted checksums.txt digest for VERSION # # Install strategy: # 1. If Node.js >= 20 is available, download the lightweight awf-bundle.js (~357KB) @@ -39,12 +40,28 @@ AWF_LIB_DIR="/usr/local/lib/awf" # Parse flags from remaining arguments ROOTLESS=false -for arg in "${@:2}"; do - case "$arg" in - --rootless) ROOTLESS=true ;; - *) echo "WARNING: Unknown argument: $arg" >&2 ;; +AWF_CHECKSUMS_SHA256="" +SEEN_CHECKSUMS_SHA256=false +if [ "$#" -gt 0 ]; then shift; fi +while [ "$#" -gt 0 ]; do + case "$1" in + --rootless) ROOTLESS=true; shift ;; + --checksums-sha256) + if [ "$SEEN_CHECKSUMS_SHA256" = true ] || [ "$#" -lt 2 ]; then + echo "ERROR: --checksums-sha256 requires exactly one digest" >&2 + exit 1 + fi + AWF_CHECKSUMS_SHA256="$2" + SEEN_CHECKSUMS_SHA256=true + shift 2 + ;; + *) echo "ERROR: Unknown AWF installer argument: $1" >&2; exit 1 ;; esac done +if [ "$SEEN_CHECKSUMS_SHA256" = true ] && [[ ! "$AWF_CHECKSUMS_SHA256" =~ ^[0-9a-f]{64}$ ]]; then + echo "ERROR: --checksums-sha256 requires 64 lowercase hexadecimal characters" >&2 + exit 1 +fi # In rootless mode, install into the user's home directory instead of /usr/local # so that standard GitHub-hosted runners (where /usr/local is root-owned) work @@ -67,7 +84,7 @@ maybe_sudo() { if [ -z "$AWF_VERSION" ]; then echo "ERROR: AWF version is required" - echo "Usage: $0 VERSION [--rootless]" + echo "Usage: $0 VERSION [--checksums-sha256 DIGEST] [--rootless]" exit 1 fi AWF_VERSION="v${AWF_VERSION#v}" @@ -75,7 +92,7 @@ if [[ ! "$AWF_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "ERROR: AWF version must be a pinned semantic release tag" >&2 exit 1 fi -CHECKSUMS_SHA256="$(release_artifact_sha256 "$AWF_REPO" "$AWF_VERSION" checksums.txt)" +CHECKSUMS_SHA256="$(release_artifact_sha256 "$AWF_REPO" "$AWF_VERSION" checksums.txt "$AWF_CHECKSUMS_SHA256")" # Detect OS and architecture OS="$(uname -s)" diff --git a/actions/setup/sh/install_copilot_cli.sh b/actions/setup/sh/install_copilot_cli.sh index f213512359f..f60ce1a3b0d 100755 --- a/actions/setup/sh/install_copilot_cli.sh +++ b/actions/setup/sh/install_copilot_cli.sh @@ -2,7 +2,7 @@ set +o histexpand # Install GitHub Copilot CLI with SHA256 checksum verification -# Usage: install_copilot_cli.sh [VERSION] [--rootless] +# Usage: install_copilot_cli.sh [VERSION] [--checksums-sha256 DIGEST] [--rootless] # # This script downloads and installs the GitHub Copilot CLI directly from GitHub # releases with SHA256 checksum verification, following the secure pattern from @@ -15,6 +15,8 @@ set +o histexpand # --rootless - Install to ~/.local/bin without sudo; appends that directory to # $GITHUB_PATH so subsequent steps find the binary. Use this on # ARC/DinD runners that enforce allowPrivilegeEscalation: false. +# --checksums-sha256 - Independently trusted SHA256SUMS.txt digest for VERSION. +# Requires a literal release version and bypasses toolcache. # # Environment overrides (testing): # COPILOT_INSTALL_DIR - Override the install directory (default: /usr/local/bin). @@ -50,20 +52,44 @@ COMPAT_MATCHED_MIN_AGENT="" COMPAT_MATCHED_MAX_AGENT="" COMPAT_CACHE_TTL_DAYS="" -# Parse arguments: treat the first non-flag argument as VERSION, all -- arguments as flags. VERSION="" ROOTLESS=false -for arg in "$@"; do - case "$arg" in - --rootless) ROOTLESS=true ;; - --*) echo "WARNING: Unknown flag: $arg" >&2 ;; +COPILOT_CHECKSUMS_SHA256="" +SEEN_CHECKSUMS_SHA256=false +while [ "$#" -gt 0 ]; do + case "$1" in + --rootless) ROOTLESS=true; shift ;; + --checksums-sha256) + if [ "$SEEN_CHECKSUMS_SHA256" = true ] || [ "$#" -lt 2 ]; then + echo "ERROR: --checksums-sha256 requires exactly one digest" >&2 + exit 1 + fi + COPILOT_CHECKSUMS_SHA256="$2" + SEEN_CHECKSUMS_SHA256=true + shift 2 + ;; + --*) echo "ERROR: Unknown Copilot installer flag: $1" >&2; exit 1 ;; *) if [ -z "$VERSION" ]; then - VERSION="$arg" + VERSION="$1" + else + echo "ERROR: Unexpected Copilot installer argument: $1" >&2 + exit 1 fi + shift ;; esac done +if [ "$SEEN_CHECKSUMS_SHA256" = true ]; then + if [[ ! "$COPILOT_CHECKSUMS_SHA256" =~ ^[0-9a-f]{64}$ ]]; then + echo "ERROR: --checksums-sha256 requires 64 lowercase hexadecimal characters" >&2 + exit 1 + fi + if [[ ! "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: --checksums-sha256 requires an explicit semantic release version, for example 1.0.98" >&2 + exit 1 + fi +fi OS="$(uname -s)" IS_WINDOWS=false @@ -643,7 +669,7 @@ stage_musl_guest_copilot() { guest_tarball="copilot-linuxmusl-${ARCH_NAME}.tar.gz" guest_archive="${TEMP_DIR}/${guest_tarball}" - manifest_digest="$(release_artifact_sha256 "$COPILOT_REPO" "$version" SHA256SUMS.txt)" || return 1 + manifest_digest="$(release_artifact_sha256 "$COPILOT_REPO" "$version" SHA256SUMS.txt "$COPILOT_CHECKSUMS_SHA256")" || return 1 echo "Staging checksum-verified ${guest_tarball} for the musl NVX guest..." curl --proto '=https' --proto-redir '=https' -fsSL --retry 5 --retry-delay 2 --retry-max-time 60 --retry-all-errors -o "$checksums_file" "${base_url}/SHA256SUMS.txt" verify_release_digest "$checksums_file" "$manifest_digest" || return 1 @@ -702,8 +728,12 @@ else echo "Explicit Copilot CLI version argument provided (${VERSION}); skipping compat matrix resolution." fi -# Prefer the runner toolcache when a compatible Copilot CLI is already available. -if CACHED_COPILOT_BIN="$(find_cached_copilot_bin "$REQUESTED_VERSION" "${COMPAT_MATCHED_MIN_AGENT}" "${COMPAT_MATCHED_MAX_AGENT}" "${COMPAT_CACHE_TTL_DAYS}")"; then +# A supplied manifest pin authenticates downloads, not a runner's existing cache. +if [ -n "$COPILOT_CHECKSUMS_SHA256" ]; then + echo "Explicit checksum-manifest pin supplied; downloading verified archives instead of using toolcache." +fi +if [ -z "$COPILOT_CHECKSUMS_SHA256" ] && + CACHED_COPILOT_BIN="$(find_cached_copilot_bin "$REQUESTED_VERSION" "${COMPAT_MATCHED_MIN_AGENT}" "${COMPAT_MATCHED_MAX_AGENT}" "${COMPAT_CACHE_TTL_DAYS}")"; then echo "Using cached GitHub Copilot CLI from ${CACHED_COPILOT_BIN}" activate_cached_copilot_bin "$CACHED_COPILOT_BIN" @@ -744,7 +774,7 @@ fi TARBALL_URL="${BASE_URL}/${TARBALL_NAME}" CHECKSUMS_URL="${BASE_URL}/SHA256SUMS.txt" -CHECKSUMS_SHA256="$(release_artifact_sha256 "$COPILOT_REPO" "$VERSION" SHA256SUMS.txt)" +CHECKSUMS_SHA256="$(release_artifact_sha256 "$COPILOT_REPO" "$VERSION" SHA256SUMS.txt "$COPILOT_CHECKSUMS_SHA256")" # Download checksums echo "Downloading checksums from ${CHECKSUMS_URL}..." @@ -819,11 +849,11 @@ if [ "$ROOTLESS" = "true" ]; then echo "ERROR: Copilot CLI installation failed - binary not found at ${INSTALL_DIR}/copilot" exit 1 fi -elif command -v copilot >/dev/null 2>&1; then - copilot --version +elif [ -x "${INSTALL_DIR}/copilot" ]; then + "${INSTALL_DIR}/copilot" --version stage_musl_guest_copilot "$VERSION" echo "✓ Copilot CLI installation complete" else - echo "ERROR: Copilot CLI installation failed - command not found" + echo "ERROR: Copilot CLI installation failed - executable not found at ${INSTALL_DIR}/copilot" exit 1 fi diff --git a/actions/setup/sh/release_integrity.sh b/actions/setup/sh/release_integrity.sh index bf3fc1c88b3..359aed79dc6 100644 --- a/actions/setup/sh/release_integrity.sh +++ b/actions/setup/sh/release_integrity.sh @@ -4,10 +4,12 @@ set +o histexpand # Sourced by installers from the trusted setup action, never from the download origin. # Review new pins against authenticated release provenance before committing them. # Each entry binds repository, immutable release version, asset name, and SHA256. +# The optional fourth argument is a literal pin from trusted workflow frontmatter. release_artifact_sha256() { - local repository="$1" version="$2" asset="$3" digest + local repository="$1" version="$2" asset="$3" digest="${4:-}" version="v${version#v}" - digest=$(awk -v repository="$repository" -v version="$version" -v asset="$asset" ' + if [ -z "$digest" ]; then + digest=$(awk -v repository="$repository" -v version="$version" -v asset="$asset" ' $1 == repository && $2 == version && $3 == asset { count++; digest = $4 } END { if (count == 1) print digest; else exit 1 } ' <<'PINS' @@ -15,10 +17,11 @@ github/gh-aw-firewall v0.28.62 checksums.txt 0b03f6ea5b638356a799fa541ade4fd7902 github/copilot-cli v1.0.98 SHA256SUMS.txt 372f0c8d3d50076455cd19ef080dc59dc2a32401d8b2d4f77ce190c8ab13bc59 github/copilot-cli v1.0.21 SHA256SUMS.txt fd9b2fb92a39aeb2a41815a6aa0bf3853307c812b3451a45f5a2217be003987d PINS - ) || { - echo "ERROR: No reviewed SHA256 pin for ${repository} ${version} ${asset}; update the trusted setup action's release_integrity.sh before installing this release" >&2 - return 1 - } + ) || { + echo "ERROR: No reviewed SHA256 pin for ${repository} ${version} ${asset}; supply checksums-sha256 in trusted workflow frontmatter or update the setup action's release_integrity.sh" >&2 + return 1 + } + fi if [[ ! "$digest" =~ ^[0-9a-f]{64}$ ]]; then echo "ERROR: Invalid reviewed SHA256 pin for ${repository} ${version} ${asset}" >&2 return 1 diff --git a/actions/setup/sh/release_integrity_test.sh b/actions/setup/sh/release_integrity_test.sh index c022dc1e871..9447547e8d1 100644 --- a/actions/setup/sh/release_integrity_test.sh +++ b/actions/setup/sh/release_integrity_test.sh @@ -21,8 +21,9 @@ manifest_digest="$(shasum -a 256 "$TEMP_DIR/checksums.txt" | awk '{print $1}')" cp "$TEMP_DIR/payload" "$TEMP_DIR/copilot-fixture/copilot" chmod +x "$TEMP_DIR/copilot-fixture/copilot" tar -czf "$TEMP_DIR/copilot-linux-x64.tar.gz" -C "$TEMP_DIR/copilot-fixture" copilot +cp "$TEMP_DIR/copilot-linux-x64.tar.gz" "$TEMP_DIR/copilot-linuxmusl-x64.tar.gz" archive_digest="$(shasum -a 256 "$TEMP_DIR/copilot-linux-x64.tar.gz" | awk '{print $1}')" -printf '%s copilot-linux-x64.tar.gz\n' "$archive_digest" >"$TEMP_DIR/SHA256SUMS.txt" +printf '%s copilot-linux-x64.tar.gz\n%s copilot-linuxmusl-x64.tar.gz\n' "$archive_digest" "$archive_digest" >"$TEMP_DIR/SHA256SUMS.txt" copilot_digest="$(shasum -a 256 "$TEMP_DIR/SHA256SUMS.txt" | awk '{print $1}')" # Test pins live only beside copies of the real installers, never in production inputs. sed -e "s/0b03f6ea5b638356a799fa541ade4fd7902a6ac8d7c3f0252ee09d8259a4ce8f/$manifest_digest/" \ @@ -59,6 +60,7 @@ case "$url" in */SHA256SUMS.txt) cp "$TEST_ROOT/SHA256SUMS.txt" "$out" ;; */awf-bundle.js|*/awf-linux-*|*/awf-darwin-*) cp "$TEST_ROOT/payload" "$out" ;; */copilot-linux-x64.tar.gz) cp "$TEST_ROOT/copilot-linux-x64.tar.gz" "$out" ;; + */copilot-linuxmusl-x64.tar.gz) cp "$TEST_ROOT/copilot-linuxmusl-x64.tar.gz" "$out" ;; *) echo "unexpected download: $url" >&2; exit 97 ;; esac EOF @@ -70,9 +72,12 @@ cat >"$TEMP_DIR/bin/uname" <<'EOF' #!/usr/bin/env bash case "$1" in -s) echo "${AWF_TEST_OS:-Linux}" ;; -m) echo "${AWF_TEST_ARCH:-x86_64}" ;; esac EOF -for command in sudo find; do - printf '#!/usr/bin/env bash\nexit 0\n' >"$TEMP_DIR/bin/$command" -done +printf '#!/usr/bin/env bash\nexit 0\n' >"$TEMP_DIR/bin/sudo" +cat >"$TEMP_DIR/bin/find" <<'EOF' +#!/usr/bin/env bash +if [ "${1:-}" = /tmp ]; then exit 0; fi +exec /usr/bin/find "$@" +EOF chmod +x "$TEMP_DIR/bin/"* run_awf() { @@ -83,6 +88,7 @@ run_awf() { run_copilot() { env TEST_ROOT="$TEMP_DIR" EXECUTION_LOG="$TEMP_DIR/executions" HOME="$TEMP_DIR/home" \ GITHUB_PATH="$TEMP_DIR/github-path" RUNNER_TOOL_CACHE="$TEMP_DIR/empty-cache" \ + GH_AW_COPILOT_GUEST_LIBC="${PIN_TEST_GUEST_LIBC:-}" RUNNER_TEMP="$TEMP_DIR/runner" \ GH_AW_COMPILED_VERSION="" PATH="$TEMP_DIR/bin:$PATH" \ bash "$TEMP_DIR/scripts/install_copilot_cli.sh" "$@" >"$TEMP_DIR/output" 2>&1 } @@ -103,6 +109,53 @@ for platform in Linux Darwin; do done echo "PASS: authenticated AWF fallback binaries cover Linux/macOS x64/arm64" +run_awf v9.9.9 --checksums-sha256 "$manifest_digest" --rootless +grep -q '/v9.9.9/awf-bundle.js' "$TEMP_DIR/curl.log" +echo "PASS: frontmatter-style AWF pin enables a different version" + +cache_bin="$TEMP_DIR/empty-cache/copilot-cli/9.9.9/x64/bin" +mkdir -p "$cache_bin" +printf '#!/usr/bin/env bash\necho CACHED_EXECUTED >>"$EXECUTION_LOG"\n' >"$cache_bin/copilot" +chmod +x "$cache_bin/copilot" +rm -f "$TEMP_DIR/curl.log" "$TEMP_DIR/executions" +PIN_TEST_GUEST_LIBC=musl run_copilot 9.9.9 --rootless --checksums-sha256 "$copilot_digest" +grep -q '/v9.9.9/copilot-linux-x64.tar.gz' "$TEMP_DIR/curl.log" +grep -q '/v9.9.9/copilot-linuxmusl-x64.tar.gz' "$TEMP_DIR/curl.log" +test -x "$TEMP_DIR/runner/gh-aw/nvx-engine/copilot" +if grep -q CACHED_EXECUTED "$TEMP_DIR/executions"; then + echo "FAIL: explicit pin was bypassed by an unverified cache hit" >&2; exit 1 +fi +rm -f "$cache_bin/copilot" "$TEMP_DIR/home/.local/bin/copilot" +echo "PASS: explicit Copilot pin verifies both host/musl archives and bypasses toolcache" + +rm -f "$TEMP_DIR/curl.log" "$TEMP_DIR/executions" +for bad_pin in "" abc "${copilot_digest^^}" '${{ inputs.pin }}'; do + if run_awf v9.9.9 --checksums-sha256 "$bad_pin" --rootless || + run_copilot 9.9.9 --checksums-sha256 "$bad_pin" --rootless; then + echo "FAIL: malformed explicit digest accepted" >&2; exit 1 + fi + test ! -e "$TEMP_DIR/curl.log" +done +if run_awf v9.9.9 --checksums-sha256 || + run_copilot 9.9.9 --checksums-sha256 || + run_awf v9.9.9 --checksums-sha256 "$manifest_digest" --checksums-sha256 "$manifest_digest" || + run_copilot 9.9.9 --checksums-sha256 "$copilot_digest" --checksums-sha256 "$copilot_digest" || + run_awf latest --checksums-sha256 "$manifest_digest" || + run_copilot latest --checksums-sha256 "$copilot_digest" || + run_copilot --checksums-sha256 "$copilot_digest"; then + echo "FAIL: duplicate, missing or unbound explicit pin accepted" >&2; exit 1 +fi +test ! -e "$TEMP_DIR/curl.log" +echo "PASS: malformed, duplicate and unbound explicit pins fail before network access" + +wrong_pin="$(printf '0%.0s' {1..64})" +if run_awf v9.9.9 --rootless --checksums-sha256 "$wrong_pin" || + run_copilot 9.9.9 --rootless --checksums-sha256 "$wrong_pin"; then + echo "FAIL: mismatched explicit manifest pin accepted" >&2; exit 1 +fi +test ! -e "$TEMP_DIR/executions" +echo "PASS: mismatched explicit pins never execute downloaded code" + # Replacing the manifest and executable together must fail before any execution. rm -f "$TEMP_DIR/executions" "$TEMP_DIR/curl.log" printf 'attacker payload\n' >"$TEMP_DIR/payload" diff --git a/docs/src/content/docs/reference/engines.md b/docs/src/content/docs/reference/engines.md index 94da44d4669..d12195c788d 100644 --- a/docs/src/content/docs/reference/engines.md +++ b/docs/src/content/docs/reference/engines.md @@ -250,7 +250,7 @@ Workflows can specify extended configuration for the coding agent: ```yaml wrap engine: id: copilot - version: latest # optional; defaults to a compiler-pinned version + version: "1.0.98" # optional; other downloads require checksums-sha256 model: gpt-5 # example override; omit to use engine default command: /usr/local/bin/copilot # custom executable path args: ["--add-dir", "/workspace"] # custom CLI arguments @@ -264,7 +264,7 @@ By default, workflows install a compiler-pinned version of each engine CLI. To o | Engine | `id` | Example `version` | |--------|------|-------------------| -| GitHub Copilot CLI | `copilot` | `"0.0.422"` | +| GitHub Copilot CLI | `copilot` | `"1.0.98"` | | Claude Code | `claude` | `"2.1.70"` | | Codex | `codex` | `"0.111.0"` | | Gemini CLI | `gemini` | `"0.31.0"` | @@ -273,11 +273,32 @@ By default, workflows install a compiler-pinned version of each engine CLI. To o ```yaml wrap engine: id: copilot - version: "0.0.422" + version: "1.0.98" ``` Pinning is useful when you need reproducible builds or want to avoid breakage from a new CLI release while testing. Remember to update the pinned version periodically to pick up bug fixes and new features. +#### Copilot release checksum pins + +To download a Copilot CLI version not covered by the setup action's built-in pins, provide `engine.checksums-sha256` alongside `engine.version`: + +```yaml wrap +engine: + id: copilot + version: "1.0.98" + checksums-sha256: "372f0c8d3d50076455cd19ef080dc59dc2a32401d8b2d4f77ce190c8ab13bc59" +``` + +The example uses the manifest digest for v1.0.98; change both fields together for another release. The digest authenticates **`SHA256SUMS.txt`**, not an individual platform archive. It must be a literal string of exactly 64 lowercase hexadecimal characters obtained through an independently trusted channel. Fetching a checksum from the same potentially intercepted origin as the executable is not independent authentication. + +An explicit pin overrides the built-in manifest pin and bypasses the runner toolcache. Both the host archive and a requested musl guest archive are verified against the authenticated manifest. Omit `command`, which replaces the built-in installer. This field is only supported by the built-in Copilot CLI installer, not other engines or inline engine definitions. + +The engine's version and pin are inherited by detection and eval jobs that reuse that engine. A separate `safe-outputs.threat-detection.engine` override accepts its own `version` and `checksums-sha256`. For AWF, use [`sandbox.agent.checksums-sha256`](/gh-aw/reference/sandbox/#release-integrity-sandboxagentchecksums-sha256). + +Without an explicit pin, the existing trusted toolcache policy and built-in release pins apply. An unreviewed download still fails closed. A supplied pin requires a literal semantic release version such as `"1.0.98"` or `"v1.0.98"`; it cannot be combined with `latest`, an omitted version, or a version expression. + +#### Parameterized versions + `version` also accepts a GitHub Actions expression string, enabling `workflow_call` reusable workflows to parameterize the engine version via caller inputs. Expressions are passed injection-safely through an environment variable rather than direct shell interpolation: ```yaml wrap @@ -286,7 +307,7 @@ on: inputs: engine-version: type: string - default: latest + default: "1.0.98" --- @@ -295,6 +316,8 @@ engine: version: ${{ inputs.engine-version }} ``` +For Copilot, a parameterized version must resolve to a trusted cached version or a release covered by the setup action's built-in manifest pins. `checksums-sha256` cannot be parameterized; use a literal version/pin pair for other releases. + ### Copilot Custom Configuration Use `agent` to reference a custom agent file in `.github/agents/` (omit the `.agent.md` extension): diff --git a/docs/src/content/docs/reference/sandbox.md b/docs/src/content/docs/reference/sandbox.md index 94791c915ad..447e85751f5 100644 --- a/docs/src/content/docs/reference/sandbox.md +++ b/docs/src/content/docs/reference/sandbox.md @@ -210,6 +210,23 @@ When omitted, AWF's own default memory limit applies. Specifying an invalid form > [!NOTE] > Exit code 137 means the process received `SIGKILL`. A memory limit can be one cause, but verify with logs before changing `memory`. If you increase `memory`, leave headroom for the runner OS and other processes. +#### Release integrity (`sandbox.agent.checksums-sha256`) + +To download an AWF version not covered by the setup action's built-in pins, set its version and checksum-manifest digest together: + +```yaml wrap +sandbox: + agent: + version: v0.28.62 + checksums-sha256: "0b03f6ea5b638356a799fa541ade4fd7902a6ac8d7c3f0252ee09d8259a4ce8f" +``` + +The example pins v0.28.62; change both fields for another release. `checksums-sha256` authenticates the release's **`checksums.txt` manifest**, which supplies digests for the Node bundle and platform binaries. It is not an individual binary or container-image digest. + +The value must contain exactly 64 lowercase hexadecimal characters from an independently trusted channel. The version must be a literal semantic release tag, with an optional `v` prefix. Missing versions, `latest`, expressions, disabled sandboxes, and custom `command` installers are rejected when a pin is supplied. Without a supplied pin, only releases covered by the setup action's built-in pins can be downloaded. + +The version and pin propagate to AWF installations in agent, threat-detection, and eval jobs. A mismatch fails installation before execution; a bundle integrity failure cannot fall back to a binary. Container-image pins and guest attestations remain separate controls. For the Copilot CLI, use [`engine.checksums-sha256`](/gh-aw/reference/engines/#copilot-release-checksum-pins). + #### Model fallback (`sandbox.agent.model-fallback`) AWF's API proxy resolves unrecognized model selections against its built-in model catalog and may rewrite them. Set `model-fallback: false` to pass the configured model through to the provider verbatim: diff --git a/pkg/parser/schema_release_integrity_test.go b/pkg/parser/schema_release_integrity_test.go new file mode 100644 index 00000000000..7bc28386823 --- /dev/null +++ b/pkg/parser/schema_release_integrity_test.go @@ -0,0 +1,34 @@ +//go:build !integration + +package parser + +import ( + "strings" + "testing" +) + +func TestMainWorkflowSchemaReleaseChecksumPins(t *testing.T) { + t.Parallel() + for _, location := range []string{"engine", "sandbox", "detection"} { + for _, pin := range []any{strings.Repeat("a", 64), "", strings.Repeat("a", 63), strings.Repeat("A", 64), "${{ inputs.pin }}", 123, true, nil} { + engine := map[string]any{"id": "copilot", "version": "1.0.99", "checksums-sha256": pin} + data := map[string]any{"on": "push", "engine": "copilot"} + switch location { + case "engine": + data["engine"] = engine + case "sandbox": + data["sandbox"] = map[string]any{"agent": map[string]any{ + "version": "v0.28.63", "checksums-sha256": pin, + }} + case "detection": + data["safe-outputs"] = map[string]any{ + "create-issue": nil, "threat-detection": map[string]any{"engine": engine}, + } + } + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(data, "release-pins.md") + if (err == nil) != (pin == strings.Repeat("a", 64)) { + t.Errorf("%s pin=%v: unexpected validation result: %v", location, pin, err) + } + } + } +} diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 457d7434378..415774b355d 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -4189,6 +4189,11 @@ "type": "string", "description": "AWF version override used to install and run the matching firewall version." }, + "checksums-sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Literal SHA256 digest of the AWF release's checksums.txt manifest. Requires an explicit semantic release version. Overrides the trusted setup action's built-in manifest pin; obtain the digest through an independently trusted channel." + }, "platform": { "type": "string", "enum": ["github.com", "ghes", "ghec", "ghec-self-hosted"], @@ -15579,6 +15584,11 @@ "description": "Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has sensible defaults and can typically be omitted. Numeric values are automatically converted to strings at runtime. GitHub Actions expressions (e.g., '${{ inputs.engine-version }}') are accepted and compiled with injection-safe env var handling.", "examples": ["beta", "stable", 20, 3.11, "${{ inputs.engine-version }}"] }, + "checksums-sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "Copilot CLI only: literal SHA256 digest of the release's SHA256SUMS.txt manifest. Requires an explicit semantic release version and bypasses the runner toolcache so the downloaded archives are verified. Overrides the trusted setup action's built-in manifest pin." + }, "model": { "type": "string", "description": "Model for this engine, overriding the top-level model. Has sensible defaults and can typically be omitted. Supports full model IDs (e.g., 'claude-3-5-sonnet-20241022', 'gpt-4'), which is useful when a nested engine (e.g. safe-outputs.threat-detection.engine) needs a different model than the main agent engine." diff --git a/pkg/workflow/compiler_validators.go b/pkg/workflow/compiler_validators.go index dd3fbde37c7..ee566d7d14a 100644 --- a/pkg/workflow/compiler_validators.go +++ b/pkg/workflow/compiler_validators.go @@ -184,6 +184,10 @@ func (c *Compiler) validateFeatureConfig(workflowData *WorkflowData, markdownPat // permission requirement, and dispatch/call-workflow configurations. // workflowPermissions is the *Permissions value returned by validatePermissions. func (c *Compiler) validateToolConfiguration(workflowData *WorkflowData, markdownPath string, workflowPermissions *Permissions) error { + workflowLog.Printf("Validating release checksum pins") + if err := c.validateReleaseIntegrityPins(workflowData); err != nil { + return formatCompilerError(markdownPath, "error", err.Error(), err) + } if err := validateExperimentalSubagentModels(workflowData); err != nil { return formatCompilerError(markdownPath, "error", err.Error(), err) } diff --git a/pkg/workflow/copilot_engine_installation.go b/pkg/workflow/copilot_engine_installation.go index e6471fd4d68..33e77a6506f 100644 --- a/pkg/workflow/copilot_engine_installation.go +++ b/pkg/workflow/copilot_engine_installation.go @@ -148,7 +148,11 @@ func (e *CopilotEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHu // the rootless location to ${RUNNER_TEMP}/gh-aw/bin/copilot where AWF expects it. rootless := isArcDindTopology(workflowData) compiledVersion := workflowData.CompiledVersion - npmSteps := generateCopilotInstallerSteps(copilotVersion, "Install GitHub Copilot CLI", rootless, compiledVersion, string(constants.CopilotSessionIDMinVersion), getNVXGuestLibc(workflowData)) + checksumsSHA256 := "" + if workflowData.EngineConfig != nil { + checksumsSHA256 = workflowData.EngineConfig.ChecksumsSHA256 + } + npmSteps := generateCopilotInstallerSteps(copilotVersion, "Install GitHub Copilot CLI", rootless, compiledVersion, string(constants.CopilotSessionIDMinVersion), getNVXGuestLibc(workflowData), checksumsSHA256) if len(inlineDriverWriteStep) > 0 { npmSteps = append(npmSteps, inlineDriverWriteStep) } @@ -428,6 +432,9 @@ func generateAWFInstallationStep(version string, agentConfig *AgentSandboxConfig } installCmd := "bash \"${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh\" " + version + if agentConfig != nil && agentConfig.ChecksumsSHA256 != "" { + installCmd += " --checksums-sha256 " + agentConfig.ChecksumsSHA256 + } // Rootless runtime profiles run AWF as the runner user: pass --rootless so the // install script installs into $HOME/.local/{bin,lib/awf} (always writable, even on // standard GitHub-hosted runners where /usr/local is root-owned) and exports diff --git a/pkg/workflow/copilot_installer.go b/pkg/workflow/copilot_installer.go index d970695d445..eeca6eed4fa 100644 --- a/pkg/workflow/copilot_installer.go +++ b/pkg/workflow/copilot_installer.go @@ -6,12 +6,15 @@ import ( var copilotInstallerLog = logger.New("workflow:copilot_installer") -func generateCopilotInstallerSteps(version, stepName string, rootless bool, compiledVersion, copilotMinVersion, guestLibc string) []GitHubActionStep { +func generateCopilotInstallerSteps(version, stepName string, rootless bool, compiledVersion, copilotMinVersion, guestLibc, checksumsSHA256 string) []GitHubActionStep { copilotInstallerLog.Printf("Generating Copilot installer steps using install_copilot_cli.sh: version=%q, rootless=%v, compiledVersion=%q, copilotMinVersion=%q", version, rootless, compiledVersion, copilotMinVersion) - rootlessFlag := "" + installerFlags := "" if rootless { - rootlessFlag = " --rootless" + installerFlags = " --rootless" + } + if checksumsSHA256 != "" { + installerFlags += " --checksums-sha256 " + checksumsSHA256 } // Use the install_copilot_cli.sh script from actions/setup/sh @@ -29,7 +32,7 @@ func generateCopilotInstallerSteps(version, stepName string, rootless bool, comp copilotInstallerLog.Printf("Version contains GitHub Actions expression, using env var for injection safety: %s", version) stepLines := []string{ " - name: " + stepName, - ` run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" "${ENGINE_VERSION}"` + rootlessFlag, + ` run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" "${ENGINE_VERSION}"` + installerFlags, " env:", " GH_HOST: github.com", " ENGINE_VERSION: " + version, @@ -45,7 +48,7 @@ func generateCopilotInstallerSteps(version, stepName string, rootless bool, comp copilotInstallerLog.Print("No explicit version; script will resolve via compat.json or baked-in default") stepLines := []string{ " - name: " + stepName, - ` run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"` + rootlessFlag, + ` run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"` + installerFlags, " env:", " GH_HOST: github.com", } @@ -55,7 +58,7 @@ func generateCopilotInstallerSteps(version, stepName string, rootless bool, comp stepLines := []string{ " - name: " + stepName, - " run: bash \"${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh\" " + version + rootlessFlag, + " run: bash \"${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh\" " + version + installerFlags, " env:", " GH_HOST: github.com", } diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index 41d63c71968..5c51d682da7 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -46,6 +46,7 @@ func toEngineEnvValueString(value any) (string, bool) { type EngineConfig struct { ID string Version string + ChecksumsSHA256 string // Trusted release checksum-manifest digest (Copilot CLI only) LLMProvider LLMProvider // Inference provider override for this engine (engine.provider / engine.model-provider) ContextWindow int // Model context window metadata for engines that need explicit model catalog entries PermissionMode string @@ -442,6 +443,9 @@ func extractReferencedEngineConfig(engineObj map[string]any, topLevel engineTopL if version, hasVersion := engineObj["version"]; hasVersion { config.Version = stringutil.ParseVersionValue(version) } + if digest, ok := engineObj["checksums-sha256"].(string); ok { + config.ChecksumsSHA256 = digest + } resolvedModel := resolveEngineModel(engineObj, topLevel, "") applyReferencedEngineFields(config, engineObj, topLevel) engineLog.Printf("Extracted engine configuration: ID=%s", config.ID) diff --git a/pkg/workflow/evals_steps.go b/pkg/workflow/evals_steps.go index c19cb1585b3..7fa5d802e70 100644 --- a/pkg/workflow/evals_steps.go +++ b/pkg/workflow/evals_steps.go @@ -224,7 +224,8 @@ func (c *Compiler) buildEvalsEngineSteps(data *WorkflowData) []string { //nolint }, SandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{ - Type: SandboxTypeAWF, + Type: SandboxTypeAWF, + ChecksumsSHA256: getAWFChecksumsSHA256(data), }, }, } diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index db36b9d2d81..b794e2d04ea 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -265,6 +265,10 @@ func (c *Compiler) extractAgentSandboxConfig(agentVal any) *AgentSandboxConfig { } } + if digest, ok := agentObj["checksums-sha256"].(string); ok { + agentConfig.ChecksumsSHA256 = digest + } + // Extract platform (AWF platform.type override) if platformVal, hasPlatform := agentObj["platform"]; hasPlatform { if platformStr, ok := platformVal.(string); ok { diff --git a/pkg/workflow/release_integrity_validation.go b/pkg/workflow/release_integrity_validation.go new file mode 100644 index 00000000000..eada61d6685 --- /dev/null +++ b/pkg/workflow/release_integrity_validation.go @@ -0,0 +1,63 @@ +package workflow + +import "regexp" + +var releaseChecksumsSHA256Pattern = regexp.MustCompile(`^[0-9a-f]{64}$`) +var pinnedReleaseVersionPattern = regexp.MustCompile(`^v?[0-9]+\.[0-9]+\.[0-9]+$`) + +func (c *Compiler) validateReleaseIntegrityPins(data *WorkflowData) error { + if data == nil { + return nil + } + if err := validateEngineReleaseIntegrityPin(data.EngineConfig, ResolveEngineID(data), "engine"); err != nil { + return err + } + if data.SafeOutputs != nil && data.SafeOutputs.ThreatDetection != nil { + config := data.SafeOutputs.ThreatDetection.EngineConfig + if config != nil { + engineID := config.ID + if engineID == "" { + engineID = c.getThreatDetectionEngineID(data) + } + if err := validateEngineReleaseIntegrityPin(config, engineID, "safe-outputs.threat-detection.engine"); err != nil { + return err + } + } + } + agent := getAgentConfig(data) + if agent == nil || agent.ChecksumsSHA256 == "" { + return nil + } + if agent.Disabled || agent.Command != "" { + return NewValidationError("sandbox.agent.checksums-sha256", agent.ChecksumsSHA256, + "checksum pins require the built-in AWF installer", + "Remove the pin for a disabled/custom sandbox, or omit sandbox.agent.command and use sandbox.agent.version: v0.28.62.") + } + return validateReleaseManifestPin("sandbox.agent", agent.Version, agent.ChecksumsSHA256) +} + +func validateEngineReleaseIntegrityPin(config *EngineConfig, engineID, field string) error { + if config == nil || config.ChecksumsSHA256 == "" { + return nil + } + if engineID != "copilot" || config.Command != "" || config.IsInlineDefinition { + return NewValidationError(field+".checksums-sha256", config.ChecksumsSHA256, + "checksum pins require the built-in Copilot CLI installer", + "Use id: copilot with an explicit version and omit command, or remove checksums-sha256 for other installers.") + } + return validateReleaseManifestPin(field, config.Version, config.ChecksumsSHA256) +} + +func validateReleaseManifestPin(field, version, digest string) error { + if !releaseChecksumsSHA256Pattern.MatchString(digest) { + return NewValidationError(field+".checksums-sha256", digest, + "expected a literal SHA256 checksum-manifest digest containing exactly 64 lowercase hexadecimal characters", + "Set checksums-sha256 to the independently trusted digest of checksums.txt (AWF) or SHA256SUMS.txt (Copilot), not an expression or an individual asset digest.") + } + if !pinnedReleaseVersionPattern.MatchString(version) { + return NewValidationError(field+".version", version, + "checksums-sha256 requires an explicit semantic release version, not latest or a runtime expression", + "Pin version and checksums-sha256 together. Example: version: \"1.0.98\" with its SHA256SUMS.txt digest.") + } + return nil +} diff --git a/pkg/workflow/release_integrity_validation_test.go b/pkg/workflow/release_integrity_validation_test.go new file mode 100644 index 00000000000..b551d85e413 --- /dev/null +++ b/pkg/workflow/release_integrity_validation_test.go @@ -0,0 +1,165 @@ +//go:build !integration + +package workflow + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testEngineChecksumsPin = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +const testAWFChecksumsPin = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + +func releasePinnedWorkflowData() *WorkflowData { + return &WorkflowData{ + Name: "release-pins", + AI: "copilot", + EngineConfig: &EngineConfig{ + ID: "copilot", Version: "1.0.99", ChecksumsSHA256: testEngineChecksumsPin, + }, + SandboxConfig: &SandboxConfig{Agent: &AgentSandboxConfig{ + Type: SandboxTypeAWF, Version: "v0.28.63", ChecksumsSHA256: testAWFChecksumsPin, + }}, + SafeOutputs: &SafeOutputsConfig{ThreatDetection: &ThreatDetectionConfig{}}, + } +} + +func TestReleaseChecksumPinExtraction(t *testing.T) { + t.Parallel() + compiler := NewCompiler() + _, engine, _ := compiler.ExtractEngineConfig(map[string]any{"engine": map[string]any{ + "id": "copilot", "version": "1.0.99", "checksums-sha256": testEngineChecksumsPin, + }}) + require.NotNil(t, engine) + assert.Equal(t, testEngineChecksumsPin, engine.ChecksumsSHA256) + agent := compiler.extractAgentSandboxConfig(map[string]any{ + "version": "v0.28.63", "checksums-sha256": testAWFChecksumsPin, + }) + require.NotNil(t, agent) + assert.Equal(t, testAWFChecksumsPin, agent.ChecksumsSHA256) +} + +func TestValidateReleaseChecksumPins(t *testing.T) { + t.Parallel() + require.NoError(t, NewCompiler().validateReleaseIntegrityPins(releasePinnedWorkflowData())) + defaultEngine := releasePinnedWorkflowData() + defaultEngine.EngineConfig.ID = "" + defaultEngine.SafeOutputs.ThreatDetection.EngineConfig = &EngineConfig{ + Version: "1.0.100", ChecksumsSHA256: testEngineChecksumsPin, + } + require.NoError(t, NewCompiler().validateReleaseIntegrityPins(defaultEngine), "default Copilot identities must accept pins") + for _, test := range []struct { + name, want string + change func(*WorkflowData) + }{ + {"engine version absent", "engine.version", func(d *WorkflowData) { d.EngineConfig.Version = "" }}, + {"engine latest", "semantic release", func(d *WorkflowData) { d.EngineConfig.Version = "latest" }}, + {"engine expression", "semantic release", func(d *WorkflowData) { d.EngineConfig.Version = "${{ inputs.version }}" }}, + {"uppercase engine digest", "64 lowercase", func(d *WorkflowData) { d.EngineConfig.ChecksumsSHA256 = strings.Repeat("A", 64) }}, + {"digest expression", "literal SHA256", func(d *WorkflowData) { d.EngineConfig.ChecksumsSHA256 = "${{ inputs.pin }}" }}, + {"other engine", "Copilot CLI installer", func(d *WorkflowData) { d.EngineConfig.ID = "claude" }}, + {"custom engine command", "Copilot CLI installer", func(d *WorkflowData) { d.EngineConfig.Command = "my-copilot" }}, + {"AWF version absent", "sandbox.agent.version", func(d *WorkflowData) { d.SandboxConfig.Agent.Version = "" }}, + {"AWF latest", "semantic release", func(d *WorkflowData) { d.SandboxConfig.Agent.Version = "latest" }}, + {"AWF short digest", "64 lowercase", func(d *WorkflowData) { d.SandboxConfig.Agent.ChecksumsSHA256 = "abc" }}, + {"AWF custom command", "AWF installer", func(d *WorkflowData) { d.SandboxConfig.Agent.Command = "my-awf" }}, + {"AWF disabled", "AWF installer", func(d *WorkflowData) { d.SandboxConfig.Agent.Disabled = true }}, + {"detection version absent", "safe-outputs.threat-detection.engine.version", func(d *WorkflowData) { + d.SafeOutputs.ThreatDetection.EngineConfig = &EngineConfig{ID: "copilot", ChecksumsSHA256: testEngineChecksumsPin} + }}, + } { + t.Run(test.name, func(t *testing.T) { + data := releasePinnedWorkflowData() + test.change(data) + err := NewCompiler().validateReleaseIntegrityPins(data) + require.Error(t, err) + assert.Contains(t, err.Error(), test.want) + }) + } +} + +func TestReleaseChecksumPinsPropagateAcrossJobs(t *testing.T) { + t.Parallel() + data := releasePinnedWorkflowData() + compiler := NewCompiler() + var main strings.Builder + for _, step := range NewCopilotEngine().GetInstallationSteps(data) { + main.WriteString(strings.Join(step, "\n")) + } + detection := strings.Join(compiler.buildInstallDetectionEngineForExternalDetectorStep(data), "") + evals := strings.Join(compiler.buildEvalsEngineSteps(data), "") + awfDetection := strings.Join(compiler.buildInstallAWFForExternalDetectorStep(data), "") + for name, content := range map[string]string{"main": main.String(), "detection": detection, "evals": evals} { + assert.Contains(t, content, "--checksums-sha256 "+testEngineChecksumsPin, name) + } + for name, content := range map[string]string{"main": main.String(), "detection": awfDetection, "evals": evals} { + assert.Contains(t, content, "--checksums-sha256 "+testAWFChecksumsPin, name) + } + assert.Equal(t, testEngineChecksumsPin, data.EngineConfig.ChecksumsSHA256) + assert.Equal(t, testAWFChecksumsPin, data.SandboxConfig.Agent.ChecksumsSHA256) +} + +func TestDetectionReleaseChecksumPinOverrides(t *testing.T) { + t.Parallel() + data := releasePinnedWorkflowData() + data.SafeOutputs.ThreatDetection.EngineConfig = &EngineConfig{ + ID: "copilot", Version: "1.0.100", ChecksumsSHA256: testAWFChecksumsPin, + } + config := resolveExternalDetectorEngineConfig(data, "copilot") + assert.Equal(t, "1.0.100", config.Version) + assert.Equal(t, testAWFChecksumsPin, config.ChecksumsSHA256) + for _, steps := range [][]string{ + NewCompiler().buildDetectionEngineExecutionStep(data), + NewCompiler().buildInstallDetectionEngineForExternalDetectorStep(data), + } { + content := strings.Join(steps, "") + assert.Contains(t, content, "--checksums-sha256 "+testAWFChecksumsPin) + assert.NotContains(t, content, "--checksums-sha256 "+testEngineChecksumsPin) + } + cloned := cloneThreatDetectionEngineConfig("copilot", &EngineConfig{ + ID: "pi", Version: "1.2.3", ChecksumsSHA256: testEngineChecksumsPin, + }) + assert.Empty(t, cloned.Version) + assert.Empty(t, cloned.ChecksumsSHA256, "a pin must not cross engine identities without its version") +} + +func TestCompileRejectsUnboundReleaseChecksumPin(t *testing.T) { + t.Parallel() + path := filepath.Join(t.TempDir(), "unbound-release-pin.md") + content := fmt.Sprintf("---\non: workflow_dispatch\nengine:\n id: copilot\n checksums-sha256: \"%s\"\n---\nInspect the repository.\n", testEngineChecksumsPin) + require.NoError(t, os.WriteFile(path, []byte(content), 0o644)) + err := NewCompiler(WithSkipValidation(true)).CompileWorkflow(path) + require.Error(t, err) + assert.Contains(t, err.Error(), "requires an explicit semantic release version") +} + +func TestCompileReleaseChecksumPins(t *testing.T) { + t.Parallel() + path := filepath.Join(t.TempDir(), "release-pins.md") + content := fmt.Sprintf(`--- +on: workflow_dispatch +strict: false +engine: + id: copilot + version: "1.0.99" + checksums-sha256: "%s" +sandbox: + agent: + version: v0.28.62 + checksums-sha256: "%s" +--- +Inspect the repository. +`, testEngineChecksumsPin, testAWFChecksumsPin) + require.NoError(t, os.WriteFile(path, []byte(content), 0o644)) + require.NoError(t, NewCompiler(WithSkipValidation(true)).CompileWorkflow(path)) + compiled, err := os.ReadFile(strings.TrimSuffix(path, ".md") + ".lock.yml") + require.NoError(t, err) + assert.Contains(t, string(compiled), `install_copilot_cli.sh" 1.0.99 --checksums-sha256 `+testEngineChecksumsPin) + assert.Contains(t, string(compiled), `install_awf_binary.sh" v0.28.62 --checksums-sha256 `+testAWFChecksumsPin) +} diff --git a/pkg/workflow/sandbox.go b/pkg/workflow/sandbox.go index d74bec655ca..ad5a112d2cf 100644 --- a/pkg/workflow/sandbox.go +++ b/pkg/workflow/sandbox.go @@ -71,25 +71,26 @@ const ( // AgentSandboxConfig represents the agent sandbox configuration type AgentSandboxConfig struct { - ID string `yaml:"id,omitempty"` // Agent ID: "awf" or "srt" (replaces Type in new object format) - Type SandboxType `yaml:"type,omitempty"` // Sandbox type: "awf" or "srt" (legacy, use ID instead) - Version string `yaml:"version,omitempty"` // AWF version override used to install and run the matching firewall version - Platform string `yaml:"platform,omitempty"` // AWF platform.type override (github.com, ghes, ghec, ghec-self-hosted) - Runtime AgentRuntime `yaml:"runtime,omitempty"` // Sandbox runtime profile for the agent container (see sandbox_runtime_profile.go) - NVX *AgentNVXConfig `yaml:"nvx,omitempty"` // Optional NVX settings; safe defaults apply when omitted - AllowHostPorts []int `yaml:"-"` // Additional host TCP ports the agent may connect to (docker-sudo-iptables only). - Disabled bool `yaml:"-"` // True when agent is explicitly set to false (disables firewall). This is a runtime flag, not serialized to YAML. - Config *SandboxRuntimeConfig `yaml:"config,omitempty"` // Custom SRT config (optional) - Command string `yaml:"command,omitempty"` // Custom command to replace AWF or SRT installation - Args []string `yaml:"args,omitempty"` // Additional arguments to append to the command - Env map[string]string `yaml:"env,omitempty"` // Environment variables to set on the step - Mounts []string `yaml:"mounts,omitempty"` // Container mounts to add for AWF (format: "source:dest:mode") - Memory string `yaml:"memory,omitempty"` // Memory limit for the AWF container (e.g., "4g", "8g") - ModelFallback *TemplatableBool `yaml:"model-fallback,omitempty"` // AWF API proxy model fallback enable/disable flag (optional) - TokenSteering *bool `yaml:"token-steering,omitempty"` // AWF API proxy token steering enable/disable flag (optional) - Targets map[string]*AgentAPIProxyTargetConfig `yaml:"targets,omitempty"` // Per-provider API proxy target overrides keyed by provider name (e.g. "openai", "anthropic") - Images map[string]string `yaml:"images,omitempty"` // Digest-pinned AWF infrastructure images keyed by AWF image role (see sandbox_agent_images.go) - CACert string `yaml:"ca-cert,omitempty"` // Host path to an additional CA certificate for API proxy upstream TLS verification (maps to apiProxy.caCert, AWF v0.28.10+) + ID string `yaml:"id,omitempty"` // Agent ID: "awf" or "srt" (replaces Type in new object format) + Type SandboxType `yaml:"type,omitempty"` // Sandbox type: "awf" or "srt" (legacy, use ID instead) + Version string `yaml:"version,omitempty"` // AWF version override used to install and run the matching firewall version + ChecksumsSHA256 string `yaml:"checksums-sha256,omitempty"` // Trusted digest of the AWF release's checksums.txt manifest + Platform string `yaml:"platform,omitempty"` // AWF platform.type override (github.com, ghes, ghec, ghec-self-hosted) + Runtime AgentRuntime `yaml:"runtime,omitempty"` // Sandbox runtime profile for the agent container (see sandbox_runtime_profile.go) + NVX *AgentNVXConfig `yaml:"nvx,omitempty"` // Optional NVX settings; safe defaults apply when omitted + AllowHostPorts []int `yaml:"-"` // Additional host TCP ports the agent may connect to (docker-sudo-iptables only). + Disabled bool `yaml:"-"` // True when agent is explicitly set to false (disables firewall). This is a runtime flag, not serialized to YAML. + Config *SandboxRuntimeConfig `yaml:"config,omitempty"` // Custom SRT config (optional) + Command string `yaml:"command,omitempty"` // Custom command to replace AWF or SRT installation + Args []string `yaml:"args,omitempty"` // Additional arguments to append to the command + Env map[string]string `yaml:"env,omitempty"` // Environment variables to set on the step + Mounts []string `yaml:"mounts,omitempty"` // Container mounts to add for AWF (format: "source:dest:mode") + Memory string `yaml:"memory,omitempty"` // Memory limit for the AWF container (e.g., "4g", "8g") + ModelFallback *TemplatableBool `yaml:"model-fallback,omitempty"` // AWF API proxy model fallback enable/disable flag (optional) + TokenSteering *bool `yaml:"token-steering,omitempty"` // AWF API proxy token steering enable/disable flag (optional) + Targets map[string]*AgentAPIProxyTargetConfig `yaml:"targets,omitempty"` // Per-provider API proxy target overrides keyed by provider name (e.g. "openai", "anthropic") + Images map[string]string `yaml:"images,omitempty"` // Digest-pinned AWF infrastructure images keyed by AWF image role (see sandbox_agent_images.go) + CACert string `yaml:"ca-cert,omitempty"` // Host path to an additional CA certificate for API proxy upstream TLS verification (maps to apiProxy.caCert, AWF v0.28.10+) } // AgentNVXConfig contains optional overrides for AWF's NVX preview runtime defaults. diff --git a/pkg/workflow/threat_detection_external.go b/pkg/workflow/threat_detection_external.go index 55ef7146588..e9d03344f4b 100644 --- a/pkg/workflow/threat_detection_external.go +++ b/pkg/workflow/threat_detection_external.go @@ -135,9 +135,10 @@ func buildThreatDetectionWorkflowData(data *WorkflowData, engineID string) *Work ContainerPinMappings: maps.Clone(data.ContainerPinMappings), SandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{ - Type: SandboxTypeAWF, - Images: maps.Clone(getSandboxAgentImages(data)), - CACert: resolveThreatDetectionCACert(data, engineID), + Type: SandboxTypeAWF, + ChecksumsSHA256: getAWFChecksumsSHA256(data), + Images: maps.Clone(getSandboxAgentImages(data)), + CACert: resolveThreatDetectionCACert(data, engineID), }, }, } @@ -172,6 +173,13 @@ func buildThreatDetectionWorkflowData(data *WorkflowData, engineID string) *Work return detectionData } +func getAWFChecksumsSHA256(data *WorkflowData) string { + if agent := getAgentConfig(data); agent != nil { + return agent.ChecksumsSHA256 + } + return "" +} + // resolveThreatDetectionCACert prefers an explicit detection CA path, honors // ca-cert: false, and otherwise inherits the agent path only for the same engine. func resolveThreatDetectionCACert(data *WorkflowData, engineID string) string { diff --git a/pkg/workflow/threat_detection_helpers.go b/pkg/workflow/threat_detection_helpers.go index 3a47fa1b299..4142133eed3 100644 --- a/pkg/workflow/threat_detection_helpers.go +++ b/pkg/workflow/threat_detection_helpers.go @@ -257,6 +257,7 @@ func cloneThreatDetectionEngineConfig(engineID string, source *EngineConfig) *En if source.ID != "" && source.ID != engineID { threatLog.Printf("Resetting runtime settings when normalizing %q to detection engine %q", source.ID, engineID) cloned.Version = "" + cloned.ChecksumsSHA256 = "" cloned.Command = "" cloned.Config = "" cloned.Args = nil diff --git a/specs/compiler-threat-detection-changelog.md b/specs/compiler-threat-detection-changelog.md index f303b78818b..207a6120dde 100644 --- a/specs/compiler-threat-detection-changelog.md +++ b/specs/compiler-threat-detection-changelog.md @@ -16,7 +16,7 @@ This changelog records the version history and the dated mapping audits for `spe | Version | Change | |---|---| -| 1.0.64 | Adds the compromised-intermediate-CA threat and maps bootstrap/runtime controls to security architecture §6.8: independently authenticated AWF/Copilot checksum manifests, compiler-pinned Compose bytes, authenticated apt signing key, HTTPS-only redirects, actual Go release-toolchain verification and bounded runtime inventory. Preserves CTR-011 destination authorization as a separate boundary and records broader download enforcement as a candidate. Unreviewed release downloads fail closed; no new threat identifier or lock-file schema change. | +| 1.0.64 | Adds the compromised-intermediate-CA threat and maps bootstrap/runtime controls to security architecture §6.8: independently authenticated AWF/Copilot checksum manifests, literal frontmatter manifest-pin overrides bound to explicit versions, compiler-pinned Compose bytes, authenticated apt signing key, HTTPS-only redirects, actual Go release-toolchain verification and bounded runtime inventory. Overrides propagate through matching detection/eval installations; Copilot overrides bypass toolcache activation. Preserves CTR-011 destination authorization as a separate boundary and records broader download enforcement as a candidate. Unreviewed release downloads without a supplied pin fail closed; no new threat identifier or lock-file schema change. | | 1.0.63 | Audit-only clarification of the Copilot SDK runtime boundary: explicit staged-only skill registration with ambient discovery disabled, hard rejection of file and symlink roots, and unchanged permission-denial counting and termination. Separates prompt recovery guidance from authorization and preserves CTR-023 engine-capability checks and CTR-028/030 generated provenance requirements. No new threat identifier or lock-file schema change. | | 1.0.62 | Added authored `pull-request-target-checkout` (CTR-008 subset) and `prompt-expression` (CTR-010 subset) rules, bringing the AST registry to 23 rules. Shared existing base-ref and literal-pair trust predicates with compiler checkout validation and reused the Actions expression visitor for prompt checks. Preserved authored strict/non-strict checkout policy, import uncertainty, exact allowlist matching, value-free prompt diagnostics, and native Actions evidence boundaries. Prompt checks reject multiline/malformed interpolations and blocked property traversal without claiming the full context allowlist. Added registry, CLI, compiler-policy, cancellation, and repository-workflow regressions. No new threat identifier or lock-file schema change. | | 1.0.61 | Added known compiler threat IDs to every built-in AST rule, propagated registry-owned references through text/JSON diagnostics and rule listings, and moved detailed lint reference content out of the CLI page. Unmapped extension rules can omit the reference. Checks URL-filter SSL prerequisites in compiler-accepted non-strict `sandbox.agent.args`; legacy `network.firewall` blocks require migration rather than an invalid SSL-setting remedy. Added mapping, output, and source-to-compiler regressions without changing the compiler schema. No new threat identifier or lock-file schema change. | diff --git a/specs/compiler-threat-detection-spec.md b/specs/compiler-threat-detection-spec.md index 569b178e841..1761d7c0e2c 100644 --- a/specs/compiler-threat-detection-spec.md +++ b/specs/compiler-threat-detection-spec.md @@ -32,7 +32,7 @@ Each version maps to the minimum compatible binary. A version change MUST update | Versions | Minimum gh-aw | Compatibility | |---|---:|---| -| `1.0.64` | Unreleased | Separates compromised-CA transport authentication from CTR-011 destination authorization. Maps trusted release digests, HTTPS-only bootstrap redirects, patched release toolchains and runtime inventory to security architecture §6.8. Unreviewed AWF/Copilot release downloads fail closed; no new `CTR-*` identifier or lock-file schema change. | +| `1.0.64` | Unreleased | Separates compromised-CA transport authentication from CTR-011 destination authorization. Maps trusted release digests, HTTPS-only bootstrap redirects, patched release toolchains and runtime inventory to security architecture §6.8. Adds optional literal `engine.checksums-sha256` and `sandbox.agent.checksums-sha256` overrides bound to explicit versions, including matching detection/eval installations. Unreviewed downloads without a supplied pin fail closed; no new `CTR-*` identifier or lock-file schema change. | | `1.0.63` | Unreleased | Audit-only; distinguishes staged Copilot SDK skill registration and runtime permission-denial enforcement from compiler controls. Preserves CTR-023 engine-capability validation and CTR-028/030 configuration provenance. No new threat identifier, permission grant, or lock-file schema change. | | `1.0.62` | Unreleased | Adds authored AST checks for `pull_request_target` checkout trust (CTR-008 subset) and multiline, malformed, or blocked-property prompt expressions (CTR-010 subset). Shares checkout trust predicates with the compiler and expression parsing with secret-flow analysis. Preserves unresolved-import uncertainty and native Actions evidence boundaries. No new threat identifier or lock-file schema change. | | `1.0.61` | Unreleased | Exposes known compiler threat IDs in AST rule metadata, text/JSON diagnostics, rule listings, and the standalone linter reference. Checks URL-filter SSL prerequisites in accepted non-strict `sandbox.agent.args`; reports schema-rejected legacy `network.firewall` as requiring migration rather than recommending an invalid remedy. No new threat identifier or lock-file schema change. | @@ -80,6 +80,8 @@ An adversary may also intercept network traffic while possessing a formerly trus Compiler-controlled bootstrap code uses independently provisioned release digests and HTTPS-only redirects as mapped in [security architecture §6.8](security-architecture-spec.md#68-transport-trust-and-bootstrap-integrity). The trusted setup action authenticates AWF and Copilot checksum manifests before consuming asset digests; threat-detect uses compiler-supplied asset digests, and Docker Compose uses compiler-supplied per-architecture digests. Release builds enforce the actual Go toolchain floor independently of `go.mod`. These are bootstrap/runtime controls, not a new `CTR-*` rejection of workflow network declarations. Broader executable-download enforcement remains a candidate under §5.3 until all engine/package-manager paths have independent authentication and deployment evidence. Compilation cannot attest arbitrary runner, proxy, MCP, or container trust stores; runtime inventory is diagnostic evidence, not conformance proof. +Authors can independently provision manifest digests through literal `engine.checksums-sha256` (Copilot) and `sandbox.agent.checksums-sha256` (AWF) frontmatter paired with explicit versions. The compiler validates these declarations and preserves their version binding through main, detection and eval installer generation; Copilot pins bypass trusted cache selection to ensure downloaded bytes are checked. These values authenticate checksum manifests, not individual executables or live TLS trust stores. Unsupported/custom installers and nonliteral or unbound pins are rejected rather than silently ignored. + Runtime trust boundaries inside the agent workspace are out of scope for this specification. In particular, the safe-outputs repository checkout discovery path (`actions/setup/js/find_repo_checkout.cjs`) resolves an `owner/repo` target from agent-writable workspace state; its normative controls — manifest precedence, workspace confinement, per-invocation `safe.directory` scoping, read-only discovery, remote host constraint, and deferred durable trust — are specified as Threat T7 and requirements RCR1–RCR7 in the Safe Outputs MCP Gateway Specification and as §3.5 of the Checkout Behavior Specification. A conforming compiler is not required to detect this class, because the compiler emits no generated-workflow construct that selects the checkout directory. PR-branch checkout authorization in `actions/setup/js/checkout_pr_branch.cjs` is also a runtime trust control, not a compiler-detectable threat. The same-repository bot exception covers `pull_request` and `pull_request_target` `opened`, `synchronize`, `ready_for_review`, and `reopened` events with matching bot sender and actor plus equal positive runtime, PR head, and PR base repository IDs. For `issue_comment`, `pull_request_review_comment`, and `pull_request_review`, an explicitly `on.bots`-allow-listed App may bypass the collaborator check only when `sender.type` is `Bot`, the sender and matching comment or review author logins match exactly, the sender is canonically equivalent to the runtime actor (App slug or `[bot]` form), the runtime repository ID is positive, and the fetched PR head and base repository IDs both match it. Forks, unlisted bots, unverifiable or mismatched identities, and other actors retain the collaborator-permission check. The normative requirements are in the Security Architecture Specification, RS-05a; this control does not add a `CTR-*` rule. diff --git a/specs/security-architecture-spec.md b/specs/security-architecture-spec.md index f68056d1018..e2105344c49 100644 --- a/specs/security-architecture-spec.md +++ b/specs/security-architecture-spec.md @@ -546,11 +546,13 @@ Destination authorization, endpoint authentication, and artifact authentication | Requirement | Contract and evidence | |---|---| | **TT-01 TLS policy** | Bootstrap curl downloads MUST restrict both initial and redirected URLs to HTTPS and MUST retain hostname/chain validation. No insecure certificate override or HTTP retry is permitted. `install_awf_binary.sh`, `install_copilot_cli.sh`, `install_threat_detect_binary.sh`, the GitHub CLI key installer, Cloud Hypervisor setup downloads, and the generated Compose installer implement this policy. | -| **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest shipped in the trusted setup action's `release_integrity.sh` before consuming it. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests; the GitHub CLI apt signing key is authenticated before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | +| **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest before consuming it. The trusted setup action's `release_integrity.sh` supplies built-in pins; literal workflow `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) and `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`) MUST allow independently provisioned overrides for explicit semantic release versions. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Explicit Copilot pins MUST bypass toolcache activation and verify both host and requested musl guest archives. Pins MUST propagate with their versions to matching detection/eval installations; engine normalization MUST NOT carry another engine's version or pin. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests; the GitHub CLI apt signing key is authenticated before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | | **TT-03 Toolchain evidence** | Release builds MUST reject stable Go toolchains older than 1.26.10 on the 1.26 branch or 1.27.3 on the 1.27 branch, unsupported older branches, and development/prerelease toolchains. `scripts/check-go-toolchain.sh` checks the actual compiler; `scripts/build-release.sh` inspects each native binary's embedded toolchain and records `dist/build-toolchain.txt`. The module's preferred toolchain and default generated Go setup are patched; user-selected Go runtimes remain author-controlled and are not attested by compilation. | | **TT-04 Runtime evidence** | Setup MUST log a bounded inventory of available host TLS consumers without credentials, environment dumps, or authenticated URLs. Missing inspection tools and uninspected container/proxy trust stores MUST be identified rather than reported as compliant. `runtime_trust_inventory.sh` supplies host versions; image operators retain responsibility for image contents and actual trust-store selection. | -The trusted bootstrap baseline includes the setup action and its digest table, compiler, GitHub Actions control plane, and runner/toolcache provisioning. It excludes attacker-controlled network responses. A preinstalled Copilot CLI from a trusted runner toolcache retains the existing cache policy; cache contents are not newly authenticated by download checks. An arbitrary runner binary, compromised setup action, or malicious workflow author is not repaired by a digest table. +The trusted bootstrap baseline includes the setup action and its digest table, literal author-supplied version/pin pairs, compiler, GitHub Actions control plane, and runner/toolcache provisioning. It excludes attacker-controlled network responses. A preinstalled Copilot CLI from a trusted runner toolcache retains the existing cache policy only when no explicit manifest pin is supplied; cache contents are not newly authenticated by download checks. An arbitrary runner binary, compromised setup action, or malicious workflow author is not repaired by a digest table. + +Compiler validation MUST reject malformed or nonliteral supplied digests, missing/dynamic versions paired with pins, and pins for unsupported or skipped installers. Only the built-in Copilot CLI installer accepts `engine.checksums-sha256`, including an explicit `safe-outputs.threat-detection.engine` override. A disabled/custom AWF sandbox or custom engine command cannot consume these pins. Without supplied pins, existing built-in digests and trusted toolcache behavior remain unchanged. `schema_release_integrity_test.go` and `release_integrity_validation_test.go` cover schema types, extraction, validation, generation, inheritance and identity boundaries; `release_integrity_test.sh` exercises custom release downloads, cache bypass and musl verification with real digest checks. #### Runtime trust inventory and ownership From a9dd386148ceef00b2bfaf212da50aeac1fa98d0 Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 10:05:16 -0700 Subject: [PATCH 4/8] Address bootstrap integrity review feedback Inline Compose verification under Bash, add isolated signing-key and diagnostic regressions, automate independently authenticated release-pin authoring, preserve reviewed musl guest cache compatibility, migrate the smoke workflow, and clarify the ADR and security conformance scope. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../smoke-copilot-dynamic-workflow.lock.yml | 40 ++--- .../smoke-copilot-dynamic-workflow.md | 2 +- Makefile | 3 + actions/setup/setup.sh | 4 +- actions/setup/sh/install_copilot_cli.sh | 7 + actions/setup/sh/install_gh_cli.sh | 2 + actions/setup/sh/install_gh_cli_test.sh | 127 +++++++++++++++ actions/setup/sh/release_integrity.sh | 1 + actions/setup/sh/runtime_trust_inventory.sh | 12 +- .../setup/sh/runtime_trust_inventory_test.sh | 87 +++++++++++ ...rap-downloads-with-bundled-release-pins.md | 15 +- pkg/cli/install_copilot_cli_test.go | 113 ++++++++++++++ pkg/workflow/copilot_engine_installation.go | 14 +- .../docker_compose_installation_test.go | 129 ++++++++++++++++ pkg/workflow/firewall_version_pinning_test.go | 14 +- scripts/update-release-pins.sh | 101 ++++++++++++ scripts/update-release-pins_test.sh | 145 ++++++++++++++++++ specs/security-architecture-spec.md | 4 +- 18 files changed, 777 insertions(+), 43 deletions(-) create mode 100644 actions/setup/sh/install_gh_cli_test.sh create mode 100644 actions/setup/sh/runtime_trust_inventory_test.sh create mode 100644 pkg/workflow/docker_compose_installation_test.go create mode 100755 scripts/update-release-pins.sh create mode 100755 scripts/update-release-pins_test.sh diff --git a/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml b/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml index cbcee5e8082..f895fecf72c 100644 --- a/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml +++ b/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"ffb6d0d7a6c4fbca1f6a8f3a679988620e6bf9cfca5750125eb1adb8b008204b","body_hash":"4f18b09f4cd3696f7e43eeddbf605ec1c9e4b260b6d0c2d096f0dcc9c0919d9c","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.92"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"be4cb06e01b936c8fcdddfbf97dae197373a9ca446de20b05055b92a5268ea23","body_hash":"4f18b09f4cd3696f7e43eeddbf605ec1c9e4b260b6d0c2d096f0dcc9c0919d9c","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_DEFAULT_OTLP_ENDPOINT","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_issue","missing_data","missing_tool","noop","report_incomplete"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -166,7 +166,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -199,8 +199,8 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.92" - GH_AW_INFO_AGENT_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" + GH_AW_INFO_AGENT_VERSION: "1.0.98" GH_AW_INFO_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GH_AW_INFO_EXPERIMENTAL: "false" @@ -516,7 +516,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "ffb6d0d7a6c4fbca1f6a8f3a679988620e6bf9cfca5750125eb1adb8b008204b:4f18b09f4cd3696f7e43eeddbf605ec1c9e4b260b6d0c2d096f0dcc9c0919d9c" + GH_AW_ADMISSION_WORKFLOW_HASH: "be4cb06e01b936c8fcdddfbf97dae197373a9ca446de20b05055b92a5268ea23:4f18b09f4cd3696f7e43eeddbf605ec1c9e4b260b6d0c2d096f0dcc9c0919d9c" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -540,7 +540,7 @@ jobs: GH_AW_ASSETS_ALLOWED_EXTS: "" GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_ENGINE_VERSION: "1.0.92" + GH_AW_ENGINE_VERSION: "1.0.98" GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_PROJECT_UTC: "-08:00" GH_AW_PR_HEAD_BASE_BRANCH: "" @@ -597,7 +597,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -646,7 +646,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.92 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.98 env: GH_HOST: github.com GH_AW_COPILOT_MIN_VERSION: 1.0.35 @@ -1399,7 +1399,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -1461,7 +1461,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/smoke-copilot-dynamic-workflow.md" GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_VERSION: "1.0.92" + GH_AW_ENGINE_VERSION: "1.0.98" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} @@ -1508,7 +1508,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/smoke-copilot-dynamic-workflow.md" GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_VERSION: "1.0.92" + GH_AW_ENGINE_VERSION: "1.0.98" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1528,7 +1528,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/smoke-copilot-dynamic-workflow.md" GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_VERSION: "1.0.92" + GH_AW_ENGINE_VERSION: "1.0.98" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1757,7 +1757,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -1858,7 +1858,7 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.92 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.98 env: GH_HOST: github.com GH_AW_COPILOT_MIN_VERSION: 1.0.35 @@ -2148,7 +2148,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -2214,7 +2214,7 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.92 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.98 env: GH_HOST: github.com GH_AW_COPILOT_MIN_VERSION: 1.0.35 @@ -2455,7 +2455,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -2507,7 +2507,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} @@ -2583,7 +2583,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.92" + GH_AW_ENGINE_VERSION: "1.0.98" GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_RUN_DETECTION: ${{ needs.detection.outputs.run_detection }} @@ -2629,7 +2629,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Copilot Dynamic Workflow" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-copilot-dynamic-workflow.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.92" + GH_AW_INFO_VERSION: "1.0.98" GH_AW_INFO_AWF_VERSION: "v0.28.62" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} diff --git a/.github/workflows/smoke-copilot-dynamic-workflow.md b/.github/workflows/smoke-copilot-dynamic-workflow.md index c9663c69853..562f6baed2d 100644 --- a/.github/workflows/smoke-copilot-dynamic-workflow.md +++ b/.github/workflows/smoke-copilot-dynamic-workflow.md @@ -12,7 +12,7 @@ permissions: copilot-requests: write engine: id: copilot - version: 1.0.92 + version: 1.0.98 dynamic-workflows: true args: ["--experimental"] checkout: diff --git a/Makefile b/Makefile index 09eba0abead..9f5f2f896df 100644 --- a/Makefile +++ b/Makefile @@ -532,6 +532,7 @@ test-scripts: build bash scripts/check-workflow-drift_test.sh ./$(BINARY_NAME) bash scripts/check-cgo-cjs-workflow-purity_test.sh bash scripts/update-threat-detect-pins_test.sh + bash scripts/update-release-pins_test.sh bash scripts/check-go-toolchain_test.sh python3 scripts/test_super_linter_scope.py python3 scripts/test_publish_safe_outputs_node.py @@ -540,8 +541,10 @@ test-scripts: build bash actions/setup/sh/create_gh_aw_tmp_dir_test.sh bash actions/setup/sh/download_docker_images_local_test.sh bash actions/setup/sh/install_awf_binary_test.sh + bash actions/setup/sh/install_gh_cli_test.sh bash actions/setup/sh/install_threat_detect_binary_test.sh bash actions/setup/sh/release_integrity_test.sh + bash actions/setup/sh/runtime_trust_inventory_test.sh @echo "✓ All script tests passed" # Test all code and guidance (Go, JavaScript, wasm golden, shell scripts, and guidance contracts) diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 8c25a09c194..5a2179fc0ed 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -210,7 +210,9 @@ fi echo "Successfully copied ${FILE_COUNT} files to ${DESTINATION}" -bash "${DESTINATION}/runtime_trust_inventory.sh" +if ! bash "${DESTINATION}/runtime_trust_inventory.sh"; then + echo "::warning::Runtime TLS inventory inspection failed; trust status unknown. Continuing setup; artifact-integrity checks remain required." +fi # Copy prompt markdown files to their expected directory PROMPTS_DEST="${GH_AW_ROOT}/prompts" diff --git a/actions/setup/sh/install_copilot_cli.sh b/actions/setup/sh/install_copilot_cli.sh index f60ce1a3b0d..d2649dbc45c 100755 --- a/actions/setup/sh/install_copilot_cli.sh +++ b/actions/setup/sh/install_copilot_cli.sh @@ -541,6 +541,13 @@ find_cached_copilot_bin() { continue fi + # A musl guest still needs a verified download even when the host uses cache. + if [ "$COPILOT_GUEST_LIBC" = "musl" ] && + ! release_artifact_sha256 "$COPILOT_REPO" "$candidate_version_normalized" SHA256SUMS.txt >/dev/null 2>&1; then + echo " Skipping candidate (no reviewed manifest pin for musl guest: ${candidate_version_normalized})" >&2 + continue + fi + if [ -n "$requested_version_normalized" ]; then if [ "$candidate_version_normalized" = "$requested_version_normalized" ]; then echo " Exact version match found: ${candidate}" >&2 diff --git a/actions/setup/sh/install_gh_cli.sh b/actions/setup/sh/install_gh_cli.sh index c508db4fe05..b03fb493e84 100644 --- a/actions/setup/sh/install_gh_cli.sh +++ b/actions/setup/sh/install_gh_cli.sh @@ -51,6 +51,8 @@ key_file="$(mktemp)" trap 'rm -f "$key_file"' EXIT curl --proto '=https' --proto-redir '=https' -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ -o "$key_file" +# Source: https://cli.github.com/packages/githubcli-archive-keyring.gpg +# Review policy recorded 2026-10-20: authenticate key rotation before updating this pin. verify_release_digest "$key_file" 6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b sudo cp "$key_file" "${KEYRING_PATH}" sudo chmod go+r "${KEYRING_PATH}" diff --git a/actions/setup/sh/install_gh_cli_test.sh b/actions/setup/sh/install_gh_cli_test.sh new file mode 100644 index 00000000000..ef7d9e41842 --- /dev/null +++ b/actions/setup/sh/install_gh_cli_test.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +# Offline tests prove fail-closed key rotation, not freshness of the remote key. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_ROOT="${PWD}/.install-gh-cli-test-$$" +mkdir "$TEST_ROOT" +trap 'rm -rf "$TEST_ROOT"' EXIT +mkdir "${TEST_ROOT}/bin" "${TEST_ROOT}/installer" + +# Keep the installer off the host PATH, including package and privilege tools. +for tool in bash basename dirname head rm; do + ln -s "$(command -v "$tool")" "${TEST_ROOT}/bin/${tool}" +done +if command -v sha256sum >/dev/null 2>&1; then + HASH_TOOL=sha256sum +else + HASH_TOOL=shasum +fi +ln -s "$(command -v "$HASH_TOOL")" "${TEST_ROOT}/bin/${HASH_TOOL}" + +printf 'reviewed test signing key\n' >"${TEST_ROOT}/key.payload" +if [ "$HASH_TOOL" = sha256sum ]; then + digest=$(sha256sum "${TEST_ROOT}/key.payload") +else + digest=$(shasum -a 256 "${TEST_ROOT}/key.payload") +fi +digest="${digest%% *}" +# Substitute only the trusted literal in a copy; production accepts no digest override. +sed -E "s/^(verify_release_digest .* )[0-9a-f]{64}$/\1${digest}/" \ + "${SCRIPT_DIR}/install_gh_cli.sh" >"${TEST_ROOT}/installer/install_gh_cli.sh" +cp "${SCRIPT_DIR}/release_integrity.sh" "${TEST_ROOT}/installer/" +grep -qF "verify_release_digest \"\$key_file\" ${digest}" \ + "${TEST_ROOT}/installer/install_gh_cli.sh" + +cat >"${TEST_ROOT}/bin/uname" <<'EOF' +#!/usr/bin/env bash +echo Linux +EOF +cat >"${TEST_ROOT}/bin/gh" <<'EOF' +#!/usr/bin/env bash +echo 'gh version test' +EOF +cat >"${TEST_ROOT}/bin/dpkg" <<'EOF' +#!/usr/bin/env bash +[[ "$*" == --print-architecture ]] || exit 95 +echo amd64 +EOF +cat >"${TEST_ROOT}/bin/apt-get" <<'EOF' +#!/usr/bin/env bash +echo "apt-get must only be called through mocked sudo" >&2 +exit 95 +EOF +cat >"${TEST_ROOT}/bin/mktemp" <<'EOF' +#!/usr/bin/env bash +[[ "$#" -eq 0 ]] || exit 95 +printf '%s\n' "${TEST_ROOT}/downloaded-key" +EOF +cat >"${TEST_ROOT}/bin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +[[ "$#" -eq 8 && "$1" == --proto && "$2" == =https && + "$3" == --proto-redir && "$4" == =https && "$5" == -fsSL && + "$6" == https://cli.github.com/packages/githubcli-archive-keyring.gpg && + "$7" == -o && "$8" == "${TEST_ROOT}/downloaded-key" ]] || exit 95 +if [[ "$KEY_MODE" == tampered ]]; then + printf 'tampered signing key\n' >"$8" +else + /bin/cp "${TEST_ROOT}/key.payload" "$8" +fi +EOF +cat >"${TEST_ROOT}/bin/sudo" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >>"${TEST_ROOT}/sudo.log" +case "$*" in + 'apt-get update -qq'|'apt-get install -y gh') ;; + "cp ${TEST_ROOT}/downloaded-key /usr/share/keyrings/githubcli-archive-keyring.gpg") + /bin/cp "$2" "${TEST_ROOT}/installed-key" + ;; + 'chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg') + /bin/chmod go+r "${TEST_ROOT}/installed-key" + ;; + 'tee /etc/apt/sources.list.d/github-cli.list') + while IFS= read -r line; do + printf '%s\n' "$line" >>"${TEST_ROOT}/source.list" + done + ;; + *) echo "Unexpected privileged operation: $*" >&2; exit 95 ;; +esac +EOF +chmod +x "${TEST_ROOT}/bin/"* +export TEST_ROOT + +run_installer() { + rm -f "${TEST_ROOT}/installed-key" "${TEST_ROOT}/source.list" + : >"${TEST_ROOT}/sudo.log" + RUN_STATUS=0 + RUN_OUTPUT=$(PATH="${TEST_ROOT}/bin" KEY_MODE="$1" \ + "${BASH}" "${TEST_ROOT}/installer/install_gh_cli.sh" 2>&1) || RUN_STATUS=$? +} + +run_installer valid +if [ "$RUN_STATUS" -ne 0 ]; then + printf 'FAIL: matching key rejected: %s\n' "$RUN_OUTPUT" >&2 + exit 1 +fi +cmp "${TEST_ROOT}/key.payload" "${TEST_ROOT}/installed-key" +grep -qxF 'deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main' \ + "${TEST_ROOT}/source.list" +grep -qxF 'apt-get install -y gh' "${TEST_ROOT}/sudo.log" +[ ! -e "${TEST_ROOT}/downloaded-key" ] +echo 'PASS: matching trusted pin installs keyring and configures the repository' + +run_installer tampered +if [ "$RUN_STATUS" -eq 0 ] || + ! grep -qF 'ERROR: Trusted SHA256 verification failed' <<<"$RUN_OUTPUT"; then + printf 'FAIL: tampered key did not fail closed: %s\n' "$RUN_OUTPUT" >&2 + exit 1 +fi +# The initial package-list refresh is allowed, but no trust-store operation is. +[ "$(cat "${TEST_ROOT}/sudo.log")" = 'apt-get update -qq' ] +[ ! -e "${TEST_ROOT}/installed-key" ] +[ ! -e "${TEST_ROOT}/source.list" ] +[ ! -e "${TEST_ROOT}/downloaded-key" ] +echo 'PASS: tampered key reports ERROR before sudo cp or package installation' diff --git a/actions/setup/sh/release_integrity.sh b/actions/setup/sh/release_integrity.sh index 359aed79dc6..f678ba70731 100644 --- a/actions/setup/sh/release_integrity.sh +++ b/actions/setup/sh/release_integrity.sh @@ -3,6 +3,7 @@ set +o histexpand # Sourced by installers from the trusted setup action, never from the download origin. # Review new pins against authenticated release provenance before committing them. +# Add pins with scripts/update-release-pins.sh using an independently trusted manifest SHA256. # Each entry binds repository, immutable release version, asset name, and SHA256. # The optional fourth argument is a literal pin from trusted workflow frontmatter. release_artifact_sha256() { diff --git a/actions/setup/sh/runtime_trust_inventory.sh b/actions/setup/sh/runtime_trust_inventory.sh index d3bafb8cb51..82a20e2c624 100644 --- a/actions/setup/sh/runtime_trust_inventory.sh +++ b/actions/setup/sh/runtime_trust_inventory.sh @@ -5,17 +5,23 @@ set -euo pipefail # Version evidence only: do not dump environment variables, proxy URLs or headers. echo "::group::Runtime TLS inventory (not a trust-store attestation)" if command -v curl >/dev/null 2>&1; then - curl --version | sed -n '1p' + if ! curl --version | sed -n '1p'; then + echo "WARNING: curl version inspection failed; trust status unknown" >&2 + fi else echo "curl: unavailable" fi if command -v node >/dev/null 2>&1; then - node -p '"Node " + process.version + "; OpenSSL " + process.versions.openssl' + if ! node -p '"Node " + process.version + "; OpenSSL " + process.versions.openssl'; then + echo "WARNING: Node/OpenSSL version inspection failed; trust status unknown" >&2 + fi else echo "Node: unavailable" fi if command -v go >/dev/null 2>&1; then - GOTOOLCHAIN=local go env GOVERSION + if ! GOTOOLCHAIN=local go env GOVERSION; then + echo "WARNING: Go version inspection failed; trust status unknown" >&2 + fi for binary in gh gh-aw; do if command -v "$binary" >/dev/null 2>&1; then if ! GOTOOLCHAIN=local go version -m "$(command -v "$binary")" | sed -n '1p'; then diff --git a/actions/setup/sh/runtime_trust_inventory_test.sh b/actions/setup/sh/runtime_trust_inventory_test.sh new file mode 100644 index 00000000000..32ed84689b7 --- /dev/null +++ b/actions/setup/sh/runtime_trust_inventory_test.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_ROOT="${PWD}/.runtime-trust-inventory-test-$$" +mkdir "$TEST_ROOT" +trap 'rm -rf "$TEST_ROOT"' EXIT +mkdir "${TEST_ROOT}/bin" "${TEST_ROOT}/destination" +for tool in bash sed; do + ln -s "$(command -v "$tool")" "${TEST_ROOT}/bin/${tool}" +done + +for tool in curl node go; do + cat >"${TEST_ROOT}/bin/${tool}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +tool="${0##*/}" +if [[ "$tool" == go && "${GOTOOLCHAIN:-}" != local ]]; then + echo 'Go inspection must not download a toolchain' >&2 + exit 95 +fi +if [[ "$FAIL_PROBE" == all || "$FAIL_PROBE" == "$tool" ]]; then + echo "${tool}: no selected runtime" >&2 + exit 1 +fi +case "$tool" in + curl) echo 'curl test TLS-backend' ;; + node) echo 'Node test; OpenSSL test' ;; + go) + if [[ "$*" == 'env GOVERSION' ]]; then + echo go-test + else + echo 'binary: go-test' + fi + ;; +esac +EOF +done +for tool in gh gh-aw; do + printf '#!/usr/bin/env bash\nexit 95\n' >"${TEST_ROOT}/bin/${tool}" +done +chmod +x "${TEST_ROOT}/bin/"* + +for probe in none go curl node all; do + status=0 + output=$(PATH="${TEST_ROOT}/bin" FAIL_PROBE="$probe" \ + "${BASH}" "${SCRIPT_DIR}/runtime_trust_inventory.sh" 2>&1) || status=$? + if [ "$status" -ne 0 ]; then + printf 'FAIL: %s probe failure aborted inventory: %s\n' "$probe" "$output" >&2 + exit 1 + fi + grep -qF '::endgroup::' <<<"$output" + grep -qF 'Browser updates and successful HTTPS connections do not attest CA distrust.' <<<"$output" + case "$probe" in + none) ! grep -qF WARNING <<<"$output" ;; + go) + grep -qF 'WARNING: Go version inspection failed; trust status unknown' <<<"$output" + grep -qF 'WARNING: gh build-toolchain inspection failed; trust status unknown' <<<"$output" + grep -qF 'WARNING: gh-aw build-toolchain inspection failed; trust status unknown' <<<"$output" + ;; + curl) grep -qF 'WARNING: curl version inspection failed; trust status unknown' <<<"$output" ;; + node) grep -qF 'WARNING: Node/OpenSSL version inspection failed; trust status unknown' <<<"$output" ;; + all) + for warning in 'Go version' 'curl version' 'Node/OpenSSL version'; do + grep -qF "WARNING: ${warning} inspection failed; trust status unknown" <<<"$output" + done + ;; + esac + echo "PASS: inventory completes with explicit diagnostics (failed probe: ${probe})" +done + +# Exercise the actual setup call site without setup's host-directory operations. +awk ' + /^if ! bash .*runtime_trust_inventory.sh/ { copying = 1 } + copying { print } + copying && /^fi$/ { exit } +' "${SCRIPT_DIR}/../setup.sh" >"${TEST_ROOT}/setup-inventory-call.sh" +[ -s "${TEST_ROOT}/setup-inventory-call.sh" ] +printf '#!/usr/bin/env bash\nexit 1\n' >"${TEST_ROOT}/destination/runtime_trust_inventory.sh" +# The child shell expands its own positional argument. +# shellcheck disable=SC2016 +output=$(DESTINATION="${TEST_ROOT}/destination" "${BASH}" -ec \ + 'source "$1"; echo setup-continued' bash "${TEST_ROOT}/setup-inventory-call.sh" 2>&1) +grep -qF '::warning::Runtime TLS inventory inspection failed; trust status unknown.' <<<"$output" +grep -qxF setup-continued <<<"$output" +echo 'PASS: setup warns and continues even when the diagnostic script exits unsuccessfully' diff --git a/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md index b55853f3b0d..8507cb3449c 100644 --- a/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md +++ b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md @@ -2,17 +2,19 @@ **Date**: 2026-10-20 **Status**: Draft -**Deciders**: pelikhan, gh-aw maintainers [TODO: verify] +**Deciders**: Pending gh-aw maintainer review in [PR #70549](https://github.com/github/gh-aw/pull/70549); no named maintainer approval is recorded here. --- ### Context -The setup action's bootstrap installers (`install_awf_binary.sh`, `install_copilot_cli.sh`, the generated Docker Compose step, and `install_gh_cli.sh`) download executables over HTTPS and check them against a checksum manifest (`checksums.txt` / `SHA256SUMS.txt`) fetched from the same release over the same channel. If a certificate authority trusted by the runner is compromised, an attacker who can intercept traffic can impersonate an allowed HTTPS host. That attacker can then replace both the artifact and its checksum file, so the checksum check provides no independent authentication. Domain allowlisting decides *where* a client may connect. It does not prove *who* answered or *what* bytes arrived. The compiler and generated Go runtime also need a toolchain that implements the relevant upstream distrust behavior. +Before this change, the AWF and Copilot CLI bootstrap installers (`install_awf_binary.sh` and `install_copilot_cli.sh`) downloaded executables over HTTPS and checked them against a checksum manifest (`checksums.txt` / `SHA256SUMS.txt`) fetched from the same release over the same channel. The generated Docker Compose step and `install_gh_cli.sh` also depend on HTTPS bootstrap downloads. If a certificate authority trusted by the runner is compromised, an attacker who can intercept traffic can impersonate an allowed HTTPS host. That attacker can then replace both an artifact and its checksum file, so the checksum check provides no independent authentication. Domain allowlisting decides *where* a client may connect. It does not prove *who* answered or *what* bytes arrived. The compiler and generated Go runtime also need a toolchain that implements the relevant upstream distrust behavior. + +The incident motivating this change is corroborated by [Mozilla's root-program announcement](https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/0R3XLJL6FuE), which reports compromised eMudhra intermediate CA private keys and describes Mozilla's blocking response. The [Go team's security release announcement](https://groups.google.com/g/golang-announce/c/66pojueaQek) identifies CVE-2026-111943 and the intermediate-certificate filtering fix in Go 1.26.10 and 1.27.3. These are primary sources for their respective responses, not evidence that every runner trust store is patched. The [incident website](https://emudhra.we-have-a-problem.com) is a secondary report, not an official eMudhra or CA/Browser Forum notice. ### Decision -We will authenticate bootstrap artifacts against SHA256 digests bound to specific releases and supplied independently of network-served checksums. A new `actions/setup/sh/release_integrity.sh` holds a reviewed pin table with entries of the form `(repository, version, asset, sha256)`. AWF and Copilot CLI installers must verify their downloaded checksum manifest against this table or a literal workflow `checksums-sha256` override before using it. Overrides belong to `sandbox.agent` for AWF and `engine` for Copilot, require an explicit semantic release version, and propagate to matching detection/eval installations. Explicit Copilot pins bypass the toolcache and verify both host and requested musl archives. Docker Compose bytes and the GitHub CLI apt signing key are pinned directly. If a release has neither a built-in nor a supplied pin, the download fails closed; `latest` cannot be paired with a supplied pin. An AWF bundle that fails verification no longer falls back to the standalone binary. All bootstrap `curl` calls are limited to HTTPS for both the initial request and any redirects (`--proto '=https' --proto-redir '=https'`). Release builds require Go 1.26.10 or newer on the 1.26 branch, 1.27.3 or newer on the 1.27 branch, or a newer supported stable release; each native binary's embedded toolchain is recorded. Together, these steps separate destination authorization, transport authentication, and artifact integrity as distinct security boundaries. +The proposed decision is to authenticate bootstrap artifacts against SHA256 digests bound to specific releases and supplied independently of network-served checksums. A new `actions/setup/sh/release_integrity.sh` holds a reviewed pin table with entries of the form `(repository, version, asset, sha256)`. AWF and Copilot CLI installers must verify their downloaded checksum manifest against this table or a literal workflow `checksums-sha256` override before using it. Overrides belong to `sandbox.agent` for AWF and `engine` for Copilot, require an explicit semantic release version, and propagate to matching detection/eval installations. Explicit Copilot pins bypass the toolcache and verify both host and requested musl archives. Docker Compose bytes and the GitHub CLI apt signing key are pinned directly; the generated Compose step must keep its verifier inside the trusted compiled step rather than sourcing a helper from a writable runtime directory. If a release has neither a built-in nor a supplied pin, the download fails closed; `latest` cannot be paired with a supplied pin. An AWF bundle that fails verification no longer falls back to the standalone binary. The hardened AWF, Copilot CLI, generated Docker Compose, and GitHub CLI installer `curl` calls are limited to HTTPS for both the initial request and any redirects (`--proto '=https' --proto-redir '=https'`); this is not a guarantee for every bootstrap download path. Release builds require Go 1.26.10 or newer on the 1.26 branch, 1.27.3 or newer on the 1.27 branch, or a newer supported stable release; each native binary's embedded toolchain is recorded. Together, these steps separate destination authorization, transport authentication, and artifact integrity as distinct security boundaries. ### Alternatives Considered @@ -22,7 +24,9 @@ Keep downloading `checksums.txt` / `SHA256SUMS.txt` alongside the artifact and r #### Alternative 2: Verify upstream cryptographic signatures or attestations (e.g., Sigstore / GitHub artifact attestations) -Verify publisher-signed provenance instead of maintaining a digest table. This would remove the manual pin updates and scale to new releases automatically. We did not choose it here because it needs signature or attestation support from every upstream (AWF, Copilot CLI, Docker Compose), plus verifier tooling and trust roots on the runner. Those trust roots would themselves need bootstrapping. The PR does not show that these prerequisites are in place. [TODO: verify whether this was evaluated] +Verify publisher-signed provenance instead of maintaining a digest table. With an enforced publisher and signer-workflow policy, signatures or attestations could reduce manual digest maintenance. This still requires trusted verifier tooling and trust roots, release/tag binding, and authenticated coverage for each selected asset; signature validity alone does not establish the intended publisher or release. + +The bounded evaluation for this ADR inspected the bootstrap installers and existing guest attestation paths and queried GitHub's attestation API for the bundled AWF v0.28.62 `checksums.txt` digest and Copilot CLI v1.0.98 `SHA256SUMS.txt` digest on 2026-10-20. Both queries returned HTTP 404. These results do not prove that attestations or signatures are absent: visibility, API access, other assets, and separately published bundles were not exhaustively evaluated. This review therefore does not establish a verified signature/attestation replacement covering the selected bootstrap manifests and Compose assets. Release-bound pins provide the bounded control in this PR; signature-based verification remains a possible future enhancement, not a rejected mechanism. Existing Cloud Hypervisor/NVX guest attestation requirements remain in force and must not fall back to digest-only verification. #### Alternative 3: Pin only the final artifact digests in the compiler (per architecture) @@ -44,9 +48,10 @@ Embed per-architecture artifact digests in compiled lock files, as threat-detect #### Neutral - Container images, MCP servers, Node/Python clients, and proxy trust stores are not patched or attested. `runtime_trust_inventory.sh` only logs diagnostic version evidence. +- The inline Maven bootstrap remains outside the hardened installer paths. This decision does not establish independent artifact authentication or HTTPS-only redirect enforcement for Maven. - The security architecture spec gains §6.8, "Transport Trust and Bootstrap Integrity" (TT-01..TT-04), and an emergency distrust procedure. Broader enforcement for package-manager downloads is recorded as a candidate rule, not an active `CTR-*` rule. - Existing threat-detect asset pins and Cloud Hypervisor/NVX attestation checks are unchanged. Trusted runner toolcache selection is unchanged when no explicit Copilot manifest pin is supplied. --- -*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* +*Draft prepared by the adr-writer agent and updated with source and implementation evidence. Maintainer acceptance remains pending; completion of this text does not change its status to Accepted.* diff --git a/pkg/cli/install_copilot_cli_test.go b/pkg/cli/install_copilot_cli_test.go index 2ca3b16c91a..ff6556d0100 100644 --- a/pkg/cli/install_copilot_cli_test.go +++ b/pkg/cli/install_copilot_cli_test.go @@ -127,6 +127,119 @@ esac assert.NotContains(t, string(curlOutput), "copilot-linux-x64.tar.gz") } +func TestInstallCopilotCLIScriptMuslCacheRequiresReviewedManifest(t *testing.T) { + t.Parallel() + wd, err := os.Getwd() + require.NoError(t, err) + projectRoot := filepath.Join(wd, "..", "..") + + for _, guestLibc := range []string{"musl", ""} { + t.Run("guest_libc="+guestLibc, func(t *testing.T) { + t.Parallel() + tempDir := t.TempDir() + toolcacheBin := filepath.Join(tempDir, "toolcache", "copilot-cli", "1.0.97", "x64", "bin") + require.NoError(t, os.MkdirAll(toolcacheBin, 0o755)) + cachedCopilot := filepath.Join(toolcacheBin, "copilot") + require.NoError(t, os.WriteFile(cachedCopilot, []byte("#!/usr/bin/env bash\necho 'copilot 1.0.97'\n"), 0o755)) + + var archive bytes.Buffer + gzipWriter := gzip.NewWriter(&archive) + tarWriter := tar.NewWriter(gzipWriter) + binary := []byte("#!/usr/bin/env bash\necho 'copilot 1.0.98'\n") + require.NoError(t, tarWriter.WriteHeader(&tar.Header{Name: "copilot", Mode: 0o755, Size: int64(len(binary)), Typeflag: tar.TypeReg})) + _, err := tarWriter.Write(binary) + require.NoError(t, err) + require.NoError(t, tarWriter.Close()) + require.NoError(t, gzipWriter.Close()) + archiveFile := filepath.Join(tempDir, "copilot.tar.gz") + require.NoError(t, os.WriteFile(archiveFile, archive.Bytes(), 0o644)) + checksum := sha256.Sum256(archive.Bytes()) + manifest := []byte(fmt.Sprintf("%x copilot-linux-x64.tar.gz\n%x copilot-linuxmusl-x64.tar.gz\n", checksum, checksum)) + checksums := filepath.Join(tempDir, "SHA256SUMS.txt") + require.NoError(t, os.WriteFile(checksums, manifest, 0o644)) + + installer, err := os.ReadFile(filepath.Join(projectRoot, "actions", "setup", "sh", "install_copilot_cli.sh")) + require.NoError(t, err) + installScript := filepath.Join(tempDir, "install_copilot_cli.sh") + require.NoError(t, os.WriteFile(installScript, installer, 0o755)) + integrity, err := os.ReadFile(filepath.Join(projectRoot, "actions", "setup", "sh", "release_integrity.sh")) + require.NoError(t, err) + lines := strings.Split(string(integrity), "\n") + for i, line := range lines { + if strings.HasPrefix(line, "github/copilot-cli v1.0.98 SHA256SUMS.txt ") { + lines[i] = fmt.Sprintf("github/copilot-cli v1.0.98 SHA256SUMS.txt %x", sha256.Sum256(manifest)) + } + } + require.NoError(t, os.WriteFile(filepath.Join(tempDir, "release_integrity.sh"), []byte(strings.Join(lines, "\n")), 0o644)) + + fakeBinDir := filepath.Join(tempDir, "fake-bin") + prepareCopilotInstallerTestBin(t, fakeBinDir) + require.NoError(t, os.WriteFile(filepath.Join(fakeBinDir, "curl"), []byte(`#!/usr/bin/env bash +set -euo pipefail +output="" +url="" +while [ "$#" -gt 0 ]; do + case "$1" in -o) output="$2"; shift 2 ;; *) url="$1"; shift ;; esac +done +echo "$url" >> "$CURL_LOG" +case "$url" in + */compat.json) + printf '%s\n' '{"agent-compat-v1":{"copilot":[{"min-gh-aw":"0.0.0","max-gh-aw":"*","min-agent":"1.0.21","max-agent":"1.0.98"}]}}' > "$output" + ;; + */v1.0.98/SHA256SUMS.txt) cp "$FIXTURE_CHECKSUMS" "$output" ;; + */v1.0.98/copilot-linux-x64.tar.gz|*/v1.0.98/copilot-linuxmusl-x64.tar.gz) cp "$FIXTURE_ARCHIVE" "$output" ;; + *) echo "unexpected URL: $url" >&2; exit 97 ;; +esac +`), 0o755)) + home := filepath.Join(tempDir, "home") + installDir := filepath.Join(home, ".local", "bin") + require.NoError(t, os.MkdirAll(installDir, 0o755)) + runnerTemp := filepath.Join(tempDir, "runner-temp") + curlLog := filepath.Join(tempDir, "curl.log") + githubPath := filepath.Join(tempDir, "github-path") + cmd := exec.Command("bash", installScript, "--rootless") + cmd.Env = append(os.Environ(), + "HOME="+home, + "RUNNER_TOOL_CACHE="+filepath.Join(tempDir, "toolcache"), + "COPILOT_INSTALL_DIR="+installDir, + "GH_AW_COMPILED_VERSION=v1.0.0", + "GH_AW_COPILOT_GUEST_LIBC="+guestLibc, + "RUNNER_TEMP="+runnerTemp, + "GITHUB_PATH="+githubPath, + "CURL_LOG="+curlLog, + "FIXTURE_CHECKSUMS="+checksums, + "FIXTURE_ARCHIVE="+archiveFile, + "PATH="+fakeBinDir+":"+os.Getenv("PATH"), + ) + output, err := cmd.CombinedOutput() + require.NoError(t, err, "%s", output) + curlOutput, err := os.ReadFile(curlLog) + require.NoError(t, err) + installed, err := os.ReadFile(filepath.Join(installDir, "copilot")) + require.NoError(t, err) + pathOutput, err := os.ReadFile(githubPath) + require.NoError(t, err) + if guestLibc == "musl" { + assert.Contains(t, string(output), "Skipping candidate (no reviewed manifest pin for musl guest: 1.0.97)") + assert.NotContains(t, string(output), "Activating cached Copilot CLI") + assert.Contains(t, string(curlOutput), "/v1.0.98/copilot-linux-x64.tar.gz") + assert.Contains(t, string(curlOutput), "/v1.0.98/copilot-linuxmusl-x64.tar.gz") + assert.Equal(t, binary, installed) + assert.NotContains(t, string(pathOutput), toolcacheBin) + staged, err := os.ReadFile(filepath.Join(runnerTemp, "gh-aw", "nvx-engine", "copilot")) + require.NoError(t, err) + assert.Equal(t, binary, staged) + } else { + assert.Contains(t, string(output), "Using cached GitHub Copilot CLI") + assert.NotContains(t, string(output), "no reviewed manifest pin for musl guest") + assert.NotContains(t, string(curlOutput), "SHA256SUMS.txt") + assert.Contains(t, string(installed), cachedCopilot) + assert.Contains(t, string(pathOutput), toolcacheBin) + } + }) + } +} + func TestInstallCopilotCLIScriptUsesToolcacheBeforeDownload(t *testing.T) { t.Parallel() wd, err := os.Getwd() diff --git a/pkg/workflow/copilot_engine_installation.go b/pkg/workflow/copilot_engine_installation.go index 33e77a6506f..1b9f34dd257 100644 --- a/pkg/workflow/copilot_engine_installation.go +++ b/pkg/workflow/copilot_engine_installation.go @@ -461,7 +461,9 @@ func generateAWFInstallationStep(version string, agentConfig *AgentSandboxConfig func generateDockerComposeInstallStep() GitHubActionStep { return GitHubActionStep([]string{ " - name: Install Docker Compose plugin", + " shell: bash", " run: |", + ` set -euo pipefail`, ` export DOCKER_CONFIG="${DOCKER_CONFIG:-$HOME/.docker}"`, ` mkdir -p "$DOCKER_CONFIG/cli-plugins"`, ` arch="$(uname -m)"`, @@ -469,8 +471,16 @@ func generateDockerComposeInstallStep() GitHubActionStep { ` download="$(mktemp)"`, ` trap 'rm -f "$download"' EXIT`, ` curl --proto '=https' --proto-redir '=https' -fsSL "https://github.com/docker/compose/releases/download/v2.36.2/docker-compose-linux-$arch" -o "$download"`, - ` source "${RUNNER_TEMP}/gh-aw/actions/release_integrity.sh"`, - ` verify_release_digest "$download" "$digest"`, + ` if command -v sha256sum >/dev/null 2>&1; then`, + ` actual="$(sha256sum "$download")"`, + ` elif command -v shasum >/dev/null 2>&1; then`, + ` actual="$(shasum -a 256 "$download")"`, + ` else`, + ` echo "ERROR: No sha256sum or shasum found" >&2; exit 1`, + ` fi`, + ` if [[ "${actual%% *}" != "$digest" ]]; then`, + ` echo "ERROR: Trusted SHA256 verification failed for Docker Compose; refusing installation" >&2; exit 1`, + ` fi`, ` install -m 0755 "$download" "$DOCKER_CONFIG/cli-plugins/docker-compose"`, ` docker compose version`, }) diff --git a/pkg/workflow/docker_compose_installation_test.go b/pkg/workflow/docker_compose_installation_test.go new file mode 100644 index 00000000000..2b124c7074c --- /dev/null +++ b/pkg/workflow/docker_compose_installation_test.go @@ -0,0 +1,129 @@ +//go:build !integration + +package workflow + +import ( + "crypto/sha256" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +func TestDockerComposeInstallerRejectsWritableVerifier(t *testing.T) { + t.Parallel() + for _, arch := range []string{"x86_64", "amd64", "aarch64", "arm64"} { + for _, tampered := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/tampered=%t", arch, tampered), func(t *testing.T) { + runDockerComposeInstaller(t, arch, tampered) + }) + } + } + t.Run("unsupported", func(t *testing.T) { + runDockerComposeInstaller(t, "riscv64", true) + }) +} + +func runDockerComposeInstaller(t *testing.T, arch string, tampered bool) { + t.Helper() + root := t.TempDir() + bin := filepath.Join(root, "bin") + helperDir := filepath.Join(root, "gh-aw", "actions") + for _, dir := range []string{bin, helperDir} { + if err := os.MkdirAll(dir, 0755); err != nil { + t.Fatal(err) + } + } + write := func(path, content string) { + t.Helper() + if err := os.WriteFile(path, []byte(content), 0755); err != nil { + t.Fatal(err) + } + } + write(filepath.Join(helperDir, "release_integrity.sh"), ` +printf sourced > "$RUNNER_TEMP/helper-sourced" +verify_release_digest() { return 0; } +`) + payload := "#!/bin/bash\nprintf executed > \"$RUNNER_TEMP/plugin-executed\"\n" + write(filepath.Join(root, "payload"), payload) + write(filepath.Join(bin, "uname"), "#!/bin/bash\nprintf '%s\\n' '"+arch+"'\n") + write(filepath.Join(bin, "mktemp"), "#!/bin/bash\nprintf '%s\\n' \"$RUNNER_TEMP/download\"\n") + write(filepath.Join(bin, "curl"), `#!/bin/bash +printf '%s\n' "$*" > "$RUNNER_TEMP/curl-args" +[[ "$1" == "--proto" && "$2" == "=https" && "$3" == "--proto-redir" && "$4" == "=https" ]] || exit 1 +cp "$RUNNER_TEMP/payload" "${@: -1}" +`) + install, err := exec.LookPath("install") + if err != nil { + t.Fatal(err) + } + write(filepath.Join(bin, "install"), "#!/bin/bash\nprintf installed > \"$RUNNER_TEMP/plugin-installed\"\nexec \""+install+"\" \"$@\"\n") + write(filepath.Join(bin, "docker"), "#!/bin/bash\n\"$DOCKER_CONFIG/cli-plugins/docker-compose\"\n") + + lines := generateDockerComposeInstallStep()[3:] + var script strings.Builder + for _, line := range lines { + script.WriteString(strings.TrimPrefix(line, " ")) + script.WriteByte('\n') + } + run := script.String() + if !tampered { + // Replace only the reviewed compiler pins with a trusted fixture pin, + // leaving the generated verifier and downloaded bytes unchanged. + fixtureDigest := fmt.Sprintf("%x", sha256.Sum256([]byte(payload))) + for _, pin := range []string{ + "9040bd35b2cc0783ce6c5de491de7e52e24d4137dbfc5de8a524f718fc23556c", + "d1148609319706a57b755ff0f61d604a63a8cf57adb24c17535baa766ff14b4f", + } { + run = strings.ReplaceAll(run, pin, fixtureDigest) + } + } + cmd := exec.Command("bash", "-c", run) + cmd.Env = append(os.Environ(), + "PATH="+bin+string(os.PathListSeparator)+os.Getenv("PATH"), + "RUNNER_TEMP="+root, + "DOCKER_CONFIG="+filepath.Join(root, "docker"), + ) + output, err := cmd.CombinedOutput() + if tampered { + if err == nil { + t.Fatalf("untrusted Compose bytes must fail: %s", output) + } + expected := "Trusted SHA256 verification failed" + if arch == "riscv64" { + expected = "Unsupported architecture" + } + if !strings.Contains(string(output), expected) { + t.Fatalf("expected %q, got %v: %s", expected, err, output) + } + } else if err != nil { + t.Fatalf("verified Compose fixture must install: %v: %s", err, output) + } + for _, marker := range []string{"helper-sourced", "plugin-installed", "plugin-executed", "docker/cli-plugins/docker-compose"} { + _, err := os.Stat(filepath.Join(root, marker)) + wantPresent := !tampered && marker != "helper-sourced" + if wantPresent && err != nil { + t.Errorf("%s should exist: %v", marker, err) + } else if !wantPresent && !os.IsNotExist(err) { + t.Errorf("%s must not exist, got %v", marker, err) + } + } + if _, err := os.Stat(filepath.Join(root, "download")); !os.IsNotExist(err) { + t.Errorf("download must be cleaned up: %v", err) + } + if arch != "riscv64" { + args, err := os.ReadFile(filepath.Join(root, "curl-args")) + if err != nil { + t.Fatal(err) + } + assetArch := "x86_64" + if arch == "arm64" || arch == "aarch64" { + assetArch = "aarch64" + } + if !strings.Contains(string(args), "https://github.com/docker/compose/releases/download/v2.36.2/docker-compose-linux-"+assetArch+" -o ") { + t.Errorf("unexpected Compose asset download: %s", args) + } + } +} diff --git a/pkg/workflow/firewall_version_pinning_test.go b/pkg/workflow/firewall_version_pinning_test.go index f31d3562661..5f915cbd3f9 100644 --- a/pkg/workflow/firewall_version_pinning_test.go +++ b/pkg/workflow/firewall_version_pinning_test.go @@ -3,7 +3,6 @@ package workflow import ( - "os" "os/exec" "path/filepath" "strings" @@ -36,8 +35,9 @@ func TestDockerComposeBootstrapIntegrity(t *testing.T) { t.Parallel() step := strings.Join(generateDockerComposeInstallStep(), "\n") for _, expected := range []string{ + " shell: bash\n run: |", `--proto '=https' --proto-redir '=https'`, - `verify_release_digest "$download" "$digest"`, + `if [[ "${actual%% *}" != "$digest" ]]; then`, "9040bd35b2cc0783ce6c5de491de7e52e24d4137dbfc5de8a524f718fc23556c", "d1148609319706a57b755ff0f61d604a63a8cf57adb24c17535baa766ff14b4f", } { @@ -45,15 +45,11 @@ func TestDockerComposeBootstrapIntegrity(t *testing.T) { t.Errorf("Compose install is missing %q", expected) } } - if strings.Index(step, "verify_release_digest") > strings.Index(step, "install -m 0755") { + if strings.Index(step, `if [[ "${actual%% *}" != "$digest" ]]; then`) > strings.Index(step, "install -m 0755") { t.Error("Compose digest verification must precede installation") } - helper, err := os.ReadFile(filepath.Join("..", "..", "actions", "setup", "sh", "release_integrity.sh")) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(helper), "verify_release_digest()") { - t.Error("generated Compose install must use the shipped verifier") + if strings.Contains(step, "release_integrity.sh") { + t.Error("generated Compose install must not source a job-writable verifier") } } diff --git a/scripts/update-release-pins.sh b/scripts/update-release-pins.sh new file mode 100755 index 00000000000..adb643f6e7b --- /dev/null +++ b/scripts/update-release-pins.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +# Usage: scripts/update-release-pins.sh REPOSITORY VERSION MANIFEST EXPECTED_SHA256 +# Obtain EXPECTED_SHA256 from an independently trusted channel (for example, +# authenticated release provenance), not another download from the release origin. +# This adds a reviewed manifest pin, never downloads or executes release binaries, +# and refuses to replace a different digest. Review the resulting diff before commit. + +fail() { + echo "error: $*" >&2 + exit 1 +} + +if [ "$#" -ne 4 ]; then + fail "Usage: $0 REPOSITORY VERSION MANIFEST EXPECTED_SHA256 (independently authenticated)" +fi +repository="$1" version="$2" asset="$3" expected="$4" +case "$repository/$asset" in + github/gh-aw-firewall/checksums.txt|github/copilot-cli/SHA256SUMS.txt) ;; + *) fail "supported repository/manifest pairs: github/gh-aw-firewall checksums.txt, github/copilot-cli SHA256SUMS.txt" ;; +esac +[[ "$version" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]] || + fail "VERSION must be a semantic release tag such as v1.0.98" +version="v${version#v}" +[[ "$expected" =~ ^[0-9a-f]{64}$ ]] || + fail "EXPECTED_SHA256 must be an independently authenticated lowercase hex64 digest" + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +target="$script_dir/../actions/setup/sh/release_integrity.sh" +[ -f "$target" ] && [ ! -L "$target" ] || fail "release_integrity.sh must be a regular, non-symlink file" + +# Keep scratch files private and on the target filesystem for atomic replacement. +work="$target.update-$$-$RANDOM" +(umask 077; mkdir "$work") || fail "cannot create private update directory" +trap 'rm -rf "$work"' EXIT +cp -p "$target" "$work/original" + +curl --disable --proto '=https' --proto-redir '=https' --fail --silent --show-error --location \ + --output "$work/manifest" \ + "https://github.com/$repository/releases/download/$version/$asset" +[ -s "$work/manifest" ] || fail "downloaded manifest is empty" +if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$work/manifest")" +elif command -v shasum >/dev/null 2>&1; then + actual="$(shasum -a 256 "$work/manifest")" +else + fail "neither sha256sum nor shasum is available" +fi +[ "${actual%% *}" = "$expected" ] || fail "manifest SHA256 mismatch; refusing to update pins" + +cp -p "$work/original" "$work/updated" +awk -v repository="$repository" -v version="$version" -v asset="$asset" -v digest="$expected" ' + function invalid(message) { + print "error: " message > "/dev/stderr" + failed = 1 + exit 1 + } + /<<.*PINS/ { + if ($0 != " \047 <<\047PINS\047" || starts++ || in_pins || ends) + invalid("could not uniquely locate the release pin heredoc") + in_pins = 1 + print + next + } + $0 == "PINS" { + if (!in_pins || ends++) invalid("unexpected release pin delimiter") + if (!found) print repository, version, asset, digest + in_pins = 0 + print + next + } + in_pins { + if (NF != 4 || + !(($1 == "github/gh-aw-firewall" && $3 == "checksums.txt") || + ($1 == "github/copilot-cli" && $3 == "SHA256SUMS.txt")) || + $2 !~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ || + length($4) != 64 || $4 !~ /^[0-9a-f]+$/) + invalid("malformed release pin row") + key = $1 SUBSEP $2 SUBSEP $3 + if (seen[key]++) invalid("duplicate release pin row") + if ($1 == repository && $2 == version && $3 == asset) { + if ($4 != digest) invalid("refusing to replace a different reviewed digest") + found = 1 + } + } + { print } + END { + if (!failed && (starts != 1 || ends != 1 || in_pins)) + invalid("expected exactly one complete release pin heredoc") + } +' "$work/original" > "$work/updated" || fail "release pin validation failed; no changes made" + +cmp -s "$target" "$work/original" || fail "release_integrity.sh changed during update; retry after review" +if cmp -s "$work/original" "$work/updated"; then + echo "Reviewed release pin already present; no changes made." +else + mv "$work/updated" "$target" + echo "Added verified manifest pin for $repository $version $asset; review the diff before committing." +fi diff --git a/scripts/update-release-pins_test.sh b/scripts/update-release-pins_test.sh new file mode 100755 index 00000000000..82a49207a86 --- /dev/null +++ b/scripts/update-release-pins_test.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +set +o histexpand +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" +work=".release-pins-test-$$-$RANDOM" +(umask 077; mkdir "$work") +trap 'rm -rf "$work"' EXIT +mkdir -p "$work/repo/scripts" "$work/repo/actions/setup/sh" "$work/bin" +cp scripts/update-release-pins.sh "$work/repo/scripts/" +cp actions/setup/sh/release_integrity.sh "$work/baseline" +target="$work/repo/actions/setup/sh/release_integrity.sh" +updater="$work/repo/scripts/update-release-pins.sh" +printf '%064d fixture-asset.tar.gz\n' 0 > "$work/manifest" +export MOCK_MANIFEST="$PWD/$work/manifest" +export MOCK_CURL_LOG="$PWD/$work/curl.log" +export MOCK_EXPECTED_URL="https://github.com/github/gh-aw-firewall/releases/download/v9.8.7/checksums.txt" +cat > "$work/bin/curl" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +echo called >> "$MOCK_CURL_LOG" +[ "$#" -eq 12 ] +[ "$1" = --disable ] +[ "$2" = --proto ] && [ "$3" = '=https' ] +[ "$4" = --proto-redir ] && [ "$5" = '=https' ] +[ "$6" = --fail ] && [ "$7" = --silent ] && [ "$8" = --show-error ] +[ "$9" = --location ] && [ "${10}" = --output ] +[ "${12}" = "$MOCK_EXPECTED_URL" ] +[ "${MOCK_CURL_FAIL:-0}" = 0 ] || exit 22 +cp "$MOCK_MANIFEST" "${11}" +MOCK +chmod +x "$work/bin/curl" +export PATH="$PWD/$work/bin:$PATH" + +hash_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + else + shasum -a 256 "$1" | awk '{print $1}' + fi +} +digest="$(hash_file "$work/manifest")" +wrong_digest="$(printf '%064d' 1)" +passed=0 + +reject() { + local name="$1" + shift + cp "$target" "$work/before" + if bash "$updater" "$@" > "$work/output" 2>&1; then + echo "FAIL: $name unexpectedly succeeded" >&2 + exit 1 + fi + cmp -s "$target" "$work/before" || { + echo "FAIL: $name modified the pin helper" >&2 + exit 1 + } + passed=$((passed + 1)) +} + +cp "$work/baseline" "$target" +reject "missing arguments" +reject "unsupported repository" evil/repository v9.8.7 checksums.txt "$digest" +reject "wrong manifest" github/gh-aw-firewall v9.8.7 SHA256SUMS.txt "$digest" +reject "asset path traversal" github/gh-aw-firewall v9.8.7 ../checksums.txt "$digest" +reject "version URL injection" github/gh-aw-firewall 'v9.8.7?token=secret' checksums.txt "$digest" +reject "prerelease tag" github/gh-aw-firewall v9.8.7-rc1 checksums.txt "$digest" +reject "invalid expected digest" github/gh-aw-firewall v9.8.7 checksums.txt BAD +reject "uppercase expected digest" github/gh-aw-firewall v9.8.7 checksums.txt "$(printf '%064d' 0 | tr 0 A)" +[ ! -e "$MOCK_CURL_LOG" ] || { echo "FAIL: unsupported input caused a download" >&2; exit 1; } +reject "manifest hash mismatch" github/gh-aw-firewall v9.8.7 checksums.txt "$wrong_digest" +export MOCK_CURL_FAIL=1 +reject "download failure" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" +unset MOCK_CURL_FAIL +cp "$work/manifest" "$work/nonempty-manifest" +: > "$work/manifest" +reject "empty manifest" github/gh-aw-firewall v9.8.7 checksums.txt "$(hash_file "$work/manifest")" +cp "$work/nonempty-manifest" "$work/manifest" + +for defect in short uppercase extra-field bad-version bad-repository bad-asset duplicate blank; do + awk -v defect="$defect" ' + /^github\/gh-aw-firewall / && !changed++ { + if (defect == "short") $4 = "abc" + if (defect == "uppercase") $4 = toupper($4) + if (defect == "extra-field") $5 = "extra" + if (defect == "bad-version") $2 = "latest" + if (defect == "bad-repository") $1 = "other/repo" + if (defect == "bad-asset") $3 = "other.txt" + if (defect == "duplicate") print + if (defect == "blank") print "" + } + { print } + ' "$work/baseline" > "$target" + reject "$defect table row" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" +done +awk '$0 != "PINS" { print }' "$work/baseline" > "$target" +reject "unterminated table" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" +awk '!/<<.*PINS/ { print }' "$work/baseline" > "$target" +reject "missing table start" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" +cp "$work/baseline" "$target" +cat "$work/baseline" >> "$target" +reject "multiple pin tables" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" + +cp "$work/baseline" "$target" +bash "$updater" github/gh-aw-firewall 9.8.7 checksums.txt "$digest" +awk -v digest="$digest" ' + $0 == "PINS" { print "github/gh-aw-firewall v9.8.7 checksums.txt " digest } + { print } +' "$work/baseline" > "$work/expected" +cmp "$target" "$work/expected" +passed=$((passed + 1)) +bash "$updater" github/gh-aw-firewall v9.8.7 checksums.txt "$digest" +cmp "$target" "$work/expected" +passed=$((passed + 1)) +printf 'another independently authenticated manifest\n' > "$work/manifest" +reject "replacement of different digest" github/gh-aw-firewall v9.8.7 checksums.txt "$(hash_file "$work/manifest")" + +export MOCK_EXPECTED_URL="https://github.com/github/copilot-cli/releases/download/v9.8.7/SHA256SUMS.txt" +digest="$(hash_file "$work/manifest")" +bash "$updater" github/copilot-cli v9.8.7 SHA256SUMS.txt "$digest" +awk -v digest="$digest" ' + $0 == "PINS" { print "github/copilot-cli v9.8.7 SHA256SUMS.txt " digest } + { print } +' "$work/expected" > "$work/expected-both" +cmp "$target" "$work/expected-both" +passed=$((passed + 1)) + +# A concurrent edit during the fetch must not be overwritten. +cat > "$work/bin/curl" <<'MOCK' +#!/usr/bin/env bash +set -euo pipefail +printf '\n# concurrent edit\n' >> "$MOCK_TARGET" +cp "$MOCK_MANIFEST" "${11}" +MOCK +export MOCK_TARGET="$PWD/$target" +cp "$target" "$work/before" +if bash "$updater" github/copilot-cli v9.8.7 SHA256SUMS.txt "$digest" > "$work/output" 2>&1; then + echo "FAIL: concurrent edit unexpectedly succeeded" >&2 + exit 1 +fi +printf '\n# concurrent edit\n' >> "$work/before" +cmp "$target" "$work/before" +passed=$((passed + 1)) +echo "PASS: $passed offline release pin authoring checks" diff --git a/specs/security-architecture-spec.md b/specs/security-architecture-spec.md index e2105344c49..8d362a6a027 100644 --- a/specs/security-architecture-spec.md +++ b/specs/security-architecture-spec.md @@ -545,8 +545,8 @@ Destination authorization, endpoint authentication, and artifact authentication | Requirement | Contract and evidence | |---|---| -| **TT-01 TLS policy** | Bootstrap curl downloads MUST restrict both initial and redirected URLs to HTTPS and MUST retain hostname/chain validation. No insecure certificate override or HTTP retry is permitted. `install_awf_binary.sh`, `install_copilot_cli.sh`, `install_threat_detect_binary.sh`, the GitHub CLI key installer, Cloud Hypervisor setup downloads, and the generated Compose installer implement this policy. | -| **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest before consuming it. The trusted setup action's `release_integrity.sh` supplies built-in pins; literal workflow `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) and `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`) MUST allow independently provisioned overrides for explicit semantic release versions. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Explicit Copilot pins MUST bypass toolcache activation and verify both host and requested musl guest archives. Pins MUST propagate with their versions to matching detection/eval installations; engine normalization MUST NOT carry another engine's version or pin. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests; the GitHub CLI apt signing key is authenticated before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | +| **TT-01 TLS policy** | The following hardened bootstrap curl paths MUST restrict both initial and redirected URLs to HTTPS and MUST retain hostname/chain validation: `install_awf_binary.sh`, `install_copilot_cli.sh`, `install_threat_detect_binary.sh`, the GitHub CLI key installer (`install_gh_cli.sh`), Cloud Hypervisor setup downloads (including `cloud_hypervisor_setup_bundle.sh`'s latest-release API probe), and the generated Compose installer in `copilot_engine_installation.go`. No insecure certificate override or HTTP retry is permitted on these paths. The inline Maven bootstrap in `copilot_engine_installation.go` remains outside this TLS policy and TT-02's independent archive-integrity guarantees. | +| **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest before consuming it. The trusted setup action's `release_integrity.sh` supplies built-in pins; literal workflow `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) and `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`) MUST allow independently provisioned overrides for explicit semantic release versions. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Explicit Copilot pins MUST bypass toolcache activation and verify both host and requested musl guest archives. Pins MUST propagate with their versions to matching detection/eval installations; engine normalization MUST NOT carry another engine's version or pin. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests and an inline verifier in the generated installer, without sourcing a job-writable helper; `install_gh_cli.sh` authenticates the GitHub CLI apt signing key before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | | **TT-03 Toolchain evidence** | Release builds MUST reject stable Go toolchains older than 1.26.10 on the 1.26 branch or 1.27.3 on the 1.27 branch, unsupported older branches, and development/prerelease toolchains. `scripts/check-go-toolchain.sh` checks the actual compiler; `scripts/build-release.sh` inspects each native binary's embedded toolchain and records `dist/build-toolchain.txt`. The module's preferred toolchain and default generated Go setup are patched; user-selected Go runtimes remain author-controlled and are not attested by compilation. | | **TT-04 Runtime evidence** | Setup MUST log a bounded inventory of available host TLS consumers without credentials, environment dumps, or authenticated URLs. Missing inspection tools and uninspected container/proxy trust stores MUST be identified rather than reported as compliant. `runtime_trust_inventory.sh` supplies host versions; image operators retain responsibility for image contents and actual trust-store selection. | From 8cf76106fa1946494e48877a4aba33861c719d49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:55:48 +0000 Subject: [PATCH 5/8] Fix reported Go and JS CI failures Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/workflows/cgo.yml | 2 +- Makefile | 2 +- actions/setup/js/daily_report_portfolio.test.cjs | 2 +- pkg/workflow/enclave_cloud_hypervisor_test.go | 10 +++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cgo.yml b/.github/workflows/cgo.yml index 6afce76aeb5..d4997cbfb61 100644 --- a/.github/workflows/cgo.yml +++ b/.github/workflows/cgo.yml @@ -1522,7 +1522,7 @@ jobs: # Downloads pre-built binary from GitHub releases instead of using go install - name: Install golangci-lint run: | - GOLANGCI_LINT_VERSION="v2.13.2" + GOLANGCI_LINT_VERSION="v2.15.0" GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) GOPATH=$(go env GOPATH) diff --git a/Makefile b/Makefile index 9f5f2f896df..49e5f7a00b5 100644 --- a/Makefile +++ b/Makefile @@ -718,7 +718,7 @@ tools: ## Install build-time tools declared in go.mod tool directives .PHONY: install-golangci-lint install-golangci-lint: @echo "Installing golangci-lint binary..." - @GOLANGCI_LINT_VERSION="v2.13.2"; \ + @GOLANGCI_LINT_VERSION="v2.15.0"; \ GOPATH=$$(go env GOPATH); \ GOOS=$$(go env GOOS); \ GOARCH=$$(go env GOARCH); \ diff --git a/actions/setup/js/daily_report_portfolio.test.cjs b/actions/setup/js/daily_report_portfolio.test.cjs index 126f0fe7a54..f4af4c25ade 100644 --- a/actions/setup/js/daily_report_portfolio.test.cjs +++ b/actions/setup/js/daily_report_portfolio.test.cjs @@ -172,7 +172,7 @@ describe("bounded daily and weekly discussion-report portfolio", () => { expect(dispatcher).not.toMatch(/^\s+workflow_dispatch:/m); expect(dispatcher).toContain(" max: 3"); const lock = fs.readFileSync(new URL("../../../.github/workflows/daily-report-dispatcher.lock.yml", import.meta.url), "utf8"); - expect(lock).toContain("if: github.run_attempt == 1"); + expect(lock).toMatch(/github\.run_attempt == 1/); expect(lock).toContain("daily_report_portfolio.cjs"); for (const profile of REPORT_PROFILES) { const worker = source(profile); diff --git a/pkg/workflow/enclave_cloud_hypervisor_test.go b/pkg/workflow/enclave_cloud_hypervisor_test.go index a26cf947de8..142aa48e546 100644 --- a/pkg/workflow/enclave_cloud_hypervisor_test.go +++ b/pkg/workflow/enclave_cloud_hypervisor_test.go @@ -249,10 +249,14 @@ INSTALLER require.NoError(t, os.WriteFile(filepath.Join(dir, "gh"), []byte(gh), 0o700)) curl := `#!/usr/bin/env bash set -euo pipefail -[[ "$2" == "https://raw.githubusercontent.com/github/gh-aw-firewall/0123456789012345678901234567890123456789/guest/cloud-hypervisor/setup-enclave-artifacts.sh" ]] -cp "${RUNNER_TEMP}/gh-aw/cloud-hypervisor-enclave-setup/setup-cloud-hypervisor-enclave-artifacts.sh" "$4" +[[ "$1" == "--proto" && "$2" == "=https" ]] +[[ "$3" == "--proto-redir" && "$4" == "=https" ]] +[[ "$5" == "-fsSL" ]] +[[ "$6" == "https://raw.githubusercontent.com/github/gh-aw-firewall/0123456789012345678901234567890123456789/guest/cloud-hypervisor/setup-enclave-artifacts.sh" ]] +[[ "$7" == "-o" ]] +cp "${RUNNER_TEMP}/gh-aw/cloud-hypervisor-enclave-setup/setup-cloud-hypervisor-enclave-artifacts.sh" "$8" if [[ "${SUBSTITUTE_INSTALLER:-}" == true ]]; then - echo '# substituted bytes' >> "$4" + echo '# substituted bytes' >> "$8" fi ` require.NoError(t, os.WriteFile(filepath.Join(dir, "curl"), []byte(curl), 0o700)) From d4db8895cbcf890fac2308a82dec0ae6aee36bb5 Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 11:37:38 -0700 Subject: [PATCH 6/8] Close idle HTTP connections before workflow leak detection The Go 1.27 toolchain moves HTTP/2 read loops into net/http/internal/http2. Close the default client's idle connections after tests finish instead of adding leak-detector exclusions for the new implementation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- pkg/workflow/test_main_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/workflow/test_main_test.go b/pkg/workflow/test_main_test.go index d2a47681c78..13eed8a5a15 100644 --- a/pkg/workflow/test_main_test.go +++ b/pkg/workflow/test_main_test.go @@ -3,6 +3,7 @@ package workflow import ( + "net/http" "testing" "go.uber.org/goleak" @@ -10,8 +11,20 @@ import ( // TestMain enforces goroutine-leak detection for all unit tests in the workflow package. func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, + goleak.VerifyTestMain(workflowTestMain{m}, goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"), goleak.IgnoreAnyFunction("net/http.(*http2clientConnReadLoop).run"), ) } + +type workflowTestMain struct { + goleak.TestingM +} + +func (m workflowTestMain) Run() int { + code := m.TestingM.Run() + // Catalog downloads share the default transport; close its idle connections + // before goleak checks for goroutines that outlive the test suite. + http.DefaultClient.CloseIdleConnections() + return code +} From e66f51e68333cbe06b8f58e5fae8e9cada7eccc6 Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 12:35:43 -0700 Subject: [PATCH 7/8] Align repository Go tooling on 1.27.3 Upgrade module, runtime, smoke, lint and devcontainer toolchain pins; refresh Go roots without inherited GOROOT for host builds and Serena; add baseline and stale-root regressions and retain only required Go 1.27 diagnostic/initializer compatibility fixes. Validation: targeted Go tests, build, formatting, WASM build and all 345 workflow recompilations passed. Full lint checkpoint remains blocked by existing custom-analyzer diagnostics and unknown-linter warnings; no checks were disabled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .changeset/compiler-transport-trust.md | 2 +- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 3 + .devcontainer/upgrade-tools.sh | 34 ++------ .github/workflows/archie.lock.yml | 6 +- .../workflows/architecture-guardian.lock.yml | 11 ++- .github/workflows/cloclo.lock.yml | 6 +- .../workflows/daily-compiler-quality.lock.yml | 2 +- .github/workflows/daily-file-diet.lock.yml | 2 +- .../workflows/daily-function-namer.lock.yml | 6 +- .../daily-testify-uber-super-expert.lock.yml | 2 +- .github/workflows/dead-code-remover.lock.yml | 11 ++- .../developer-docs-consolidator.lock.yml | 2 +- .../duplicate-code-detector.lock.yml | 2 +- .../workflows/glossary-maintainer.lock.yml | 6 +- .github/workflows/go-fan.lock.yml | 2 +- .github/workflows/go.mod | 2 +- .github/workflows/linter-miner.lock.yml | 6 +- .github/workflows/mcp-inspector.lock.yml | 6 +- .github/workflows/purelock.lock.yml | 6 +- .github/workflows/sergo.lock.yml | 2 +- .github/workflows/shared/mcp/serena-go.md | 7 +- .github/workflows/smoke-claude.lock.yml | 17 +++- .github/workflows/smoke-claude.md | 2 +- .github/workflows/smoke-codex.lock.yml | 19 +++-- .github/workflows/smoke-codex.md | 2 +- .../smoke-copilot-aoai-apikey.lock.yml | 21 +++-- .../workflows/smoke-copilot-aoai-apikey.md | 2 +- .../smoke-copilot-aoai-entra.lock.yml | 21 +++-- .github/workflows/smoke-copilot-aoai-entra.md | 2 +- .github/workflows/smoke-copilot-arm.lock.yml | 19 +++-- .github/workflows/smoke-copilot-arm.md | 2 +- .github/workflows/smoke-copilot.lock.yml | 21 +++-- .github/workflows/smoke-copilot.md | 2 +- .github/workflows/smoke-cursor.lock.yml | 17 +++- .github/workflows/smoke-cursor.md | 2 +- .github/workflows/smoke-goose.lock.yml | 17 +++- .github/workflows/smoke-goose.md | 2 +- .github/workflows/smoke-kiro.lock.yml | 17 +++- .github/workflows/smoke-kiro.md | 2 +- .github/workflows/smoke-test-tools.lock.yml | 17 +++- .github/workflows/smoke-test-tools.md | 2 +- .github/workflows/spec-extractor.lock.yml | 2 +- .github/workflows/spec-librarian.lock.yml | 2 +- .github/workflows/terminal-stylist.lock.yml | 6 +- .github/workflows/typist.lock.yml | 2 +- .golangci.yml | 2 +- CHANGELOG.md | 6 +- ...rap-downloads-with-bundled-release-pins.md | 4 +- go.mod | 4 +- pkg/cli/logs_episode_retrieval_artifacts.go | 3 +- pkg/cli/outcome_eval_agent.go | 6 +- pkg/cli/token_usage_subagent_session.go | 4 +- pkg/constants/version_constants.go | 2 +- pkg/workflow/agy_engine.go | 16 ++-- pkg/workflow/assign_to_user.go | 2 +- pkg/workflow/behavior_defined_engine.go | 18 ++--- pkg/workflow/config_parsing_helpers_test.go | 6 +- pkg/workflow/create_project.go | 2 +- pkg/workflow/create_project_status_update.go | 2 +- pkg/workflow/dependabot_manifests.go | 2 +- pkg/workflow/hide_comment.go | 2 +- pkg/workflow/link_sub_issue.go | 2 +- pkg/workflow/reply_to_pr_review_comment.go | 2 +- pkg/workflow/runtime_go_baseline_test.go | 81 +++++++++++++++++++ pkg/workflow/runtime_go_root_test.go | 56 +++++++++++++ pkg/workflow/runtime_setup_test.go | 2 +- pkg/workflow/runtime_step_generator.go | 18 ++++- pkg/workflow/serena_go_runtime_test.go | 10 +-- pkg/workflow/update_discussion_test.go | 4 +- pkg/workflow/update_entity_helpers_test.go | 8 +- pkg/workflow/update_issue_test.go | 4 +- pkg/workflow/update_project.go | 2 +- scripts/check-go-toolchain.sh | 4 +- scripts/check-go-toolchain_test.sh | 4 +- specs/security-architecture-spec.md | 2 +- 76 files changed, 434 insertions(+), 192 deletions(-) create mode 100644 pkg/workflow/runtime_go_baseline_test.go create mode 100644 pkg/workflow/runtime_go_root_test.go diff --git a/.changeset/compiler-transport-trust.md b/.changeset/compiler-transport-trust.md index 47688a6ec17..380c9783131 100644 --- a/.changeset/compiler-transport-trust.md +++ b/.changeset/compiler-transport-trust.md @@ -2,6 +2,6 @@ "gh-aw": major --- -Harden executable bootstrap against compromised certificate authorities with trusted release-manifest digests, HTTPS-only redirects, pinned Compose and apt-key bytes, patched Go release toolchains, and bounded runtime TLS inventory. +Harden executable bootstrap against compromised certificate authorities with trusted release-manifest digests, HTTPS-only redirects, pinned Compose and apt-key bytes, a Go 1.27.3 repository/tooling baseline, and bounded runtime TLS inventory. Refresh selected Go runtime roots before host builds and Serena exposure to prevent stale GOROOT toolchain mismatches. **Breaking change:** Unreviewed AWF/Copilot downloads without a supplied pin and `latest` downloads fail closed. Use a reviewed built-in release or provide an explicit version with `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) or `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`). These literal frontmatter pins propagate to matching detection/eval installations and must come from an independently trusted channel. Explicit Copilot pins bypass the toolcache; cache behavior without a supplied pin is unchanged. Updating the trusted setup action's built-in digest table remains supported. See security architecture §6.8 for emergency distrust and per-runtime ownership. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d41dcd0a449..934ee594b55 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/go:1-bookworm +FROM mcr.microsoft.com/devcontainers/base:bookworm # Remove the broken yarn apt source with expired GPG key RUN rm -f /etc/apt/sources.list.d/yarn.list diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7f4fd6fc66f..19b07ef42e6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -44,6 +44,9 @@ "ghcr.io/devcontainers/features/docker-in-docker:4.0.0": {}, "ghcr.io/devcontainers/features/git-lfs:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.27.3" + }, "ghcr.io/devcontainers/features/node:2.1.0": { "version": "24" } diff --git a/.devcontainer/upgrade-tools.sh b/.devcontainer/upgrade-tools.sh index 17609681326..50621940eae 100755 --- a/.devcontainer/upgrade-tools.sh +++ b/.devcontainer/upgrade-tools.sh @@ -1,35 +1,13 @@ #!/bin/bash -# Upgrade Go and the GitHub CLI (gh) to their latest released versions. -# Intended to run on devcontainer post-create so the environment always -# picks up the newest toolchain. +# Verify the pinned Go feature and upgrade the GitHub CLI (gh). set -euo pipefail -ARCH="$(dpkg --print-architecture 2>/dev/null || uname -m)" -case "$ARCH" in - x86_64) ARCH="amd64" ;; - aarch64) ARCH="arm64" ;; -esac - -# --- Upgrade Go to the latest stable version --- -echo "Determining latest Go version..." -LATEST_GO="$(curl -fsSL https://go.dev/VERSION?m=text | head -n1)" -if [ -z "${LATEST_GO:-}" ]; then - echo "Failed to determine latest Go version; skipping Go upgrade." >&2 -else - CURRENT_GO="$(go version 2>/dev/null | awk '{print $3}' || echo "none")" - if [ "$CURRENT_GO" = "$LATEST_GO" ]; then - echo "Go is already up to date ($CURRENT_GO)." - else - echo "Upgrading Go from ${CURRENT_GO} to ${LATEST_GO}..." - TARBALL="${LATEST_GO}.linux-${ARCH}.tar.gz" - TMP="$(mktemp -d)" - curl -fsSL "https://go.dev/dl/${TARBALL}" -o "${TMP}/${TARBALL}" - sudo rm -rf /usr/local/go - sudo tar -C /usr/local -xzf "${TMP}/${TARBALL}" - rm -rf "${TMP}" - echo "Go upgraded to $(/usr/local/go/bin/go version | awk '{print $3}')." - fi +CURRENT_GO="$(env -u GOROOT GOTOOLCHAIN=local go version | awk '{print $3}')" +if [ "$CURRENT_GO" != go1.27.3 ]; then + echo "ERROR: Expected Go 1.27.3 from the pinned devcontainer feature; found ${CURRENT_GO}. Rebuild the devcontainer." >&2 + exit 1 fi +echo "Go tooling is pinned to ${CURRENT_GO}." # --- Upgrade the GitHub CLI (gh) to the latest release --- echo "Upgrading GitHub CLI (gh)..." diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 1add4d45fa3..618762b1ada 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a796f5d2e7b1ee01db414fdd1d4357f25534bdc315136d9d6f9fcb5f5e82b474","body_hash":"27b1dc98801c43d5c5a4390216637f3938e5e293e25ac9757c34c0be61740ff5","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0d6de4d9f573a5c691aee889c5e610f1091290088db54d0f4a39c02af740ef2f","body_hash":"7bd3fd14476b452c8c202b0034e4c697fbbf65eb6fb1ab2309c6318387124b6b","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -537,7 +537,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "a796f5d2e7b1ee01db414fdd1d4357f25534bdc315136d9d6f9fcb5f5e82b474:27b1dc98801c43d5c5a4390216637f3938e5e293e25ac9757c34c0be61740ff5" + GH_AW_ADMISSION_WORKFLOW_HASH: "0d6de4d9f573a5c691aee889c5e610f1091290088db54d0f4a39c02af740ef2f:7bd3fd14476b452c8c202b0034e4c697fbbf65eb6fb1ab2309c6318387124b6b" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -724,7 +724,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/architecture-guardian.lock.yml b/.github/workflows/architecture-guardian.lock.yml index 65adf1d775d..78f6f75c8f7 100644 --- a/.github/workflows/architecture-guardian.lock.yml +++ b/.github/workflows/architecture-guardian.lock.yml @@ -596,7 +596,16 @@ jobs: go-version-file: go.mod cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 3055ebdf8be..60bddba26b2 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4052494b0d60f7e3efc49640cb9731799e3d7346e10b6d36a47291c7970bd538","body_hash":"7c32be945ee0f2f96f56a0e518297c9dfb429f25c97238af4dee92f4e751e8e5","strict":true,"agent_id":"codex","agent_model":"copilot/gpt-5.6-terra","engine_versions":{"codex":"0.163.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"67343d02fd8da6244e5f9691facd99ac94aedaaca0c725676fb802a7ab6cb01a","body_hash":"566dae91e84337338e9c2a4411601c98965248713ea79f30105344bd2036e49c","strict":true,"agent_id":"codex","agent_model":"copilot/gpt-5.6-terra","engine_versions":{"codex":"0.163.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","create_pull_request","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -635,7 +635,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "4052494b0d60f7e3efc49640cb9731799e3d7346e10b6d36a47291c7970bd538:7c32be945ee0f2f96f56a0e518297c9dfb429f25c97238af4dee92f4e751e8e5" + GH_AW_ADMISSION_WORKFLOW_HASH: "67343d02fd8da6244e5f9691facd99ac94aedaaca0c725676fb802a7ab6cb01a:566dae91e84337338e9c2a4411601c98965248713ea79f30105344bd2036e49c" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -888,7 +888,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/daily-compiler-quality.lock.yml b/.github/workflows/daily-compiler-quality.lock.yml index e5973fcaf9e..ff8081c7658 100644 --- a/.github/workflows/daily-compiler-quality.lock.yml +++ b/.github/workflows/daily-compiler-quality.lock.yml @@ -765,7 +765,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 7bcb05aafe3..8e78ada0d38 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -665,7 +665,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/daily-function-namer.lock.yml b/.github/workflows/daily-function-namer.lock.yml index b98131ff205..d8e10c3980d 100644 --- a/.github/workflows/daily-function-namer.lock.yml +++ b/.github/workflows/daily-function-namer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cb150fe57fa2c174daf80c0b23dfb7ef23394aea08096443e35042d449358288","body_hash":"d9efcf80c56c282135224713667d2bc2bdf2cef364eaf2d588a80dfbd516cf5f","strict":true,"agent_id":"pi","agent_model":"copilot/claude-sonnet-5","engine_versions":{"pi":"1.3.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"dea08c91e8fcb35a64e23f3f1c59bcac045540f435dd0856011f2aa52bc579f3","body_hash":"e0d05ec7559e35331f697ebbc13ccd57241d87c5885a1587dfbc87b450629613","strict":true,"agent_id":"pi","agent_model":"copilot/claude-sonnet-5","engine_versions":{"pi":"1.3.0"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"safeoutputs","tools":["create_discussion","create_issue","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -472,7 +472,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "cb150fe57fa2c174daf80c0b23dfb7ef23394aea08096443e35042d449358288:d9efcf80c56c282135224713667d2bc2bdf2cef364eaf2d588a80dfbd516cf5f" + GH_AW_ADMISSION_WORKFLOW_HASH: "dea08c91e8fcb35a64e23f3f1c59bcac045540f435dd0856011f2aa52bc579f3:e0d05ec7559e35331f697ebbc13ccd57241d87c5885a1587dfbc87b450629613" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -684,7 +684,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 54af4b11289..19eda56127e 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -691,7 +691,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index 5ef551aa67c..20200798b52 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -628,7 +628,16 @@ jobs: go-version-file: go.mod cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 28f38f2619b..f0879fdd5b1 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -734,7 +734,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index d0024d75fd4..0df71d3f3fe 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -663,7 +663,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/router:0.28.62@sha256:fb222d01480a2e8eda09f59e9bb8fdda04b0940e24fd6868d233ad24125cf85d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 0807f4d6d94..cb9ad846dd2 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"75914c41c87ee0d8393aba835ee39a5173c9c13705b4d59a125ce6b495374da4","body_hash":"3a147cd7b30fc4be335228d569ec59e2a6a5b3c9cf8a068cc9fbb71ad7dd513c","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c76cbb0615fbc80bf6542952778167f8a96033905f0033fc6b4f88da7ecdb475","body_hash":"8773963a2f8d531a3802b1ccaa224a6226c2f3c35e8d986ba8d2dd2f51167688","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_pull_request","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -518,7 +518,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "75914c41c87ee0d8393aba835ee39a5173c9c13705b4d59a125ce6b495374da4:3a147cd7b30fc4be335228d569ec59e2a6a5b3c9cf8a068cc9fbb71ad7dd513c" + GH_AW_ADMISSION_WORKFLOW_HASH: "c76cbb0615fbc80bf6542952778167f8a96033905f0033fc6b4f88da7ecdb475:8773963a2f8d531a3802b1ccaa224a6226c2f3c35e8d986ba8d2dd2f51167688" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -752,7 +752,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index 5ff8d430d6d..e68086b7342 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -734,7 +734,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/go.mod b/.github/workflows/go.mod index a537eb2f9b9..1f439487bb0 100644 --- a/.github/workflows/go.mod +++ b/.github/workflows/go.mod @@ -1,5 +1,5 @@ module github.com/github/gh-aw-workflows-deps -go 1.21 +go 1.27.3 require ( ) diff --git a/.github/workflows/linter-miner.lock.yml b/.github/workflows/linter-miner.lock.yml index faab7251d86..0e0104086a8 100644 --- a/.github/workflows/linter-miner.lock.yml +++ b/.github/workflows/linter-miner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9a4a79a5212d006d57557320b5977e9b4bb510f5c831aeb14437899bed19b539","body_hash":"e60eb2552be6b726b8d0ebda9023911e1651fe20e79810d4fe2a02cdd25d646b","strict":true,"agent_id":"copilot","agent_model":"copilot/claude-sonnet-5.5","engine_versions":{"copilot":"1.0.98","copilot-sdk":"1.0.16"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cf704a989c728ddd9c071bb1b4c24873c2393f6cb0067f226bc447992ff6fdb9","body_hash":"08781937f04bed268872af52a0c0470a9ec52172df91fd8894fa44ec8dcb6f95","strict":true,"agent_id":"copilot","agent_model":"copilot/claude-sonnet-5.5","engine_versions":{"copilot":"1.0.98","copilot-sdk":"1.0.16"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_discussion","get_discussion_comments","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_discussion_categories","list_discussions","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_pull_request","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -499,7 +499,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "9a4a79a5212d006d57557320b5977e9b4bb510f5c831aeb14437899bed19b539:e60eb2552be6b726b8d0ebda9023911e1651fe20e79810d4fe2a02cdd25d646b" + GH_AW_ADMISSION_WORKFLOW_HASH: "cf704a989c728ddd9c071bb1b4c24873c2393f6cb0067f226bc447992ff6fdb9:08781937f04bed268872af52a0c0470a9ec52172df91fd8894fa44ec8dcb6f95" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -710,7 +710,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Preload linter source and cache context run: "set -euo pipefail\nmkdir -p /tmp/gh-aw/agent\n\n: > /tmp/gh-aw/agent/linters-src.txt\nwhile IFS= read -r -d '' file; do\n printf '\\n===== FILE: %s =====\\n' \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\n cat \"$file\" >> /tmp/gh-aw/agent/linters-src.txt\ndone < <(find pkg/linters -type f -name '*.go' -print0 | sort -z)\ncat .github/skills/go-linters/SKILL.md > /tmp/gh-aw/agent/go-linters-skill.txt\n\nprior_file=\"\"\nif [ -d /tmp/gh-aw/cache-memory ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters.json' | sort | head -n 1 || true)\"\n if [ -z \"${prior_file}\" ]; then\n prior_file=\"$(find /tmp/gh-aw/cache-memory -maxdepth 4 -type f -name 'proposed-linters' | sort | head -n 1 || true)\"\n fi\nfi\nif [ -n \"${prior_file}\" ] && [ -f \"${prior_file}\" ]; then\n cp \"${prior_file}\" /tmp/gh-aw/agent/prior-linters.json\nelse\n echo \"[]\" > /tmp/gh-aw/agent/prior-linters.json\nfi" diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 6b7253512fb..e62a3ea52ff 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e3bffe043f0d8668bbbdc19796625a9c7cb36ac3dbe5686df912dde8b50a90cf","body_hash":"bb224508df015d9338a66ae5bd19ddead95a847c8a5445622b610d96d28bba2e","agent_id":"copilot","engine_versions":{"copilot":"1.0.98","copilot-sdk":"1.0.16"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"09179ca798aaa0bca0ffe83dbb15cbdbc726d464cbf519340024ed22b1146ebf","body_hash":"1d8292a606626b5a69e6d65cbdedc14ceeaeb58a9fdb6abc1e2de9df4f7f4706","agent_id":"copilot","engine_versions":{"copilot":"1.0.98","copilot-sdk":"1.0.16"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","DD_API_KEY","DD_APPLICATION_KEY","DD_APP_KEY","DD_SITE","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","SENTRY_ACCESS_TOKEN","SENTRY_OPENAI_API_KEY","SLACK_BOT_TOKEN","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"},{"image":"node:lts-alpine","digest":"sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1","pinned_image":"node:lts-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"ast-grep","tools":["*"]},{"name":"datadog","tools":["get_datadog_metric","get_datadog_trace","search_datadog_dashboards","search_datadog_metrics","search_datadog_slos","search_datadog_spans"]},{"name":"deepwiki","tools":["ask_question","read_wiki_contents","read_wiki_structure"]},{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"microsoftdocs","tools":["*"]},{"name":"safeoutputs","tools":["create_discussion","missing_data","missing_tool","noop","post_to_slack_channel","report_incomplete"]},{"name":"sentry","tools":["analyze_issue_with_seer","find_dsns","find_organizations","find_projects","find_releases","find_teams","get_doc","get_event_attachment","get_issue_details","get_trace_details","list_events","list_issue_events","search_docs","search_events","search_issues","whoami"]},{"name":"serena","tools":["*"]},{"name":"tavily","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -506,7 +506,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "e3bffe043f0d8668bbbdc19796625a9c7cb36ac3dbe5686df912dde8b50a90cf:bb224508df015d9338a66ae5bd19ddead95a847c8a5445622b610d96d28bba2e" + GH_AW_ADMISSION_WORKFLOW_HASH: "09179ca798aaa0bca0ffe83dbb15cbdbc726d464cbf519340024ed22b1146ebf:1d8292a606626b5a69e6d65cbdedc14ceeaeb58a9fdb6abc1e2de9df4f7f4706" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -750,7 +750,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 node:lts-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 diff --git a/.github/workflows/purelock.lock.yml b/.github/workflows/purelock.lock.yml index b078fcaa747..d3a29d64c2a 100644 --- a/.github/workflows/purelock.lock.yml +++ b/.github/workflows/purelock.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d61338e53e788a861b5eaf786ec3c2cb9ec201251c38f56f1fb81efd1a82496a","body_hash":"aed7d99969bfb427c523267d19d6e61bd4768323d5fd167e10da9fe95d777c04","strict":true,"agent_id":"codex","agent_model":"copilot/gpt-5.6-terra","engine_versions":{"codex":"0.163.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"05251f9962d4d450478802c9889f65b1dfb9f79ff460f721fafde100d45aa6d8","body_hash":"6db8d71b7086abd59a9046dad4317eb619c6e8012ee81873ab7a87dddee7a94d","strict":true,"agent_id":"codex","agent_model":"copilot/gpt-5.6-terra","engine_versions":{"codex":"0.163.0"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"actions/upload-code-coverage","sha":"2b21a77928be8d5168c2b9581a67f2adbebacc52","version":"v1.4.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_pull_request","missing_data","missing_tool","noop","report_incomplete","upload_code_coverage"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -504,7 +504,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "d61338e53e788a861b5eaf786ec3c2cb9ec201251c38f56f1fb81efd1a82496a:aed7d99969bfb427c523267d19d6e61bd4768323d5fd167e10da9fe95d777c04" + GH_AW_ADMISSION_WORKFLOW_HASH: "05251f9962d4d450478802c9889f65b1dfb9f79ff460f721fafde100d45aa6d8:6db8d71b7086abd59a9046dad4317eb619c6e8012ee81873ab7a87dddee7a94d" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -728,7 +728,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 802988ae868..354b9d04b03 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -683,7 +683,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/shared/mcp/serena-go.md b/.github/workflows/shared/mcp/serena-go.md index 0201186f4c5..052a18acfe0 100644 --- a/.github/workflows/shared/mcp/serena-go.md +++ b/.github/workflows/shared/mcp/serena-go.md @@ -28,7 +28,8 @@ pre-agent-steps: - name: Expose Go to Serena container run: | set -euo pipefail - go_root="$(go env GOROOT)" + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" case "$go_root" in "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"/go/*) ;; *) @@ -40,6 +41,8 @@ pre-agent-steps: echo "Serena Go runtime has no executable bin/go in the mounted toolchain." >&2 exit 1 fi + export GOROOT="$go_root" + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" printf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\n' "$go_root" "$PATH" >> "$GITHUB_ENV" "$go_root/bin/go" version --- @@ -57,6 +60,8 @@ and appending the gateway's existing PATH. No additional host paths are mounted. The existing runner tool-cache mount remains read-only. The setup check verifies the runner toolchain and mount boundary, not language-server readiness: a healthy MCP transport alone does not prove Go semantic analysis works. +Root discovery clears any earlier `GOROOT`, disables automatic toolchain selection, +and refreshes the host `GOROOT` so later builds use the same selected toolchain. If Serena initialization fails, report the language-server error and stop tasks that require semantic analysis; do not substitute text-only analysis or install tools ad hoc. diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 03a6e6068b2..ac419554297 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e5c061c7fa142fbb9ad06761177ea29aa3c45b3b53499030f9fab8a1f8264aef","body_hash":"5f6944eddd4844652e8741847bc7c01061d0cedf3193fb809e6114eed875cd33","strict":true,"agent_id":"claude","agent_model":"claude-sonnet-4-6","engine_versions":{"claude":"2.1.296"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"84c8e276b81b77c443f0f064258f90d68250fd897b70a0d57664c56781782025","body_hash":"5f6944eddd4844652e8741847bc7c01061d0cedf3193fb809e6114eed875cd33","strict":true,"agent_id":"claude","agent_model":"claude-sonnet-4-6","engine_versions":{"claude":"2.1.296"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","TAVILY_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"},{"repo":"github/codeql-action/upload-sarif","sha":"6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee","version":"v4.39.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"has_pull_request":true,"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"mcpscripts","tools":["go","make"]},{"name":"safeoutputs","tools":["add_comment","add_labels","add_reviewer","close_pull_request","create_check_run","create_code_scanning_alert","create_issue","create_pull_request_review_comment","missing_data","missing_tool","noop","post_slack_message","push_to_pull_request_branch","report_incomplete","resolve_pull_request_review_thread","submit_pull_request_review","update_pull_request"]},{"name":"tavily","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -577,7 +577,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "e5c061c7fa142fbb9ad06761177ea29aa3c45b3b53499030f9fab8a1f8264aef:5f6944eddd4844652e8741847bc7c01061d0cedf3193fb809e6114eed875cd33" + GH_AW_ADMISSION_WORKFLOW_HASH: "84c8e276b81b77c443f0f064258f90d68250fd897b70a0d57664c56781782025:5f6944eddd4844652e8741847bc7c01061d0cedf3193fb809e6114eed875cd33" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -737,10 +737,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index b6f07f10372..65a2a9eef1d 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -74,7 +74,7 @@ tools: - "*" runtimes: go: - version: "1.26" + version: "1.27.3" checkout: fetch: ["*"] fetch-depth: 0 diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index b4feba33915..ecc684277d0 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4eade3d14f422842dfab467fbf3f0212b89307d5da64d112368ae0d4143b4c4a","body_hash":"971a1bd89429bf149cdc42a60f675515a3de5ce46b0d52ac7a76ea85530c1302","strict":true,"agent_id":"codex","engine_versions":{"codex":"0.163.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4201e520713e3e860cad271fdb92935ecf2c4fd855a1393a086bed43a93ef013","body_hash":"b35df4f11739d28b83d6ef503d2905fad09eea96cbe54ba816cd18e38d6b29c0","strict":true,"agent_id":"codex","engine_versions":{"codex":"0.163.0"}} # gh-aw-manifest: {"version":1,"secrets":["CODEX_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions-ecosystem/action-add-labels","sha":"18f1af5e3544586314bbe15c0273249c770b2daf","version":"v1.1.3"},{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","list_branches","list_commits","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","add_smoked_label","create_issue","hide_comment","missing_data","missing_tool","noop","remove_labels","report_incomplete","set_issue_field","unassign_from_user"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -577,7 +577,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "4eade3d14f422842dfab467fbf3f0212b89307d5da64d112368ae0d4143b4c4a:971a1bd89429bf149cdc42a60f675515a3de5ce46b0d52ac7a76ea85530c1302" + GH_AW_ADMISSION_WORKFLOW_HASH: "4201e520713e3e860cad271fdb92935ecf2c4fd855a1393a086bed43a93ef013:b35df4f11739d28b83d6ef503d2905fad09eea96cbe54ba816cd18e38d6b29c0" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -696,10 +696,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: @@ -834,7 +843,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index c5d0b3a58b8..ef7290ea6e6 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -59,7 +59,7 @@ tools: web-fetch: runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] add-comment: diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 668162eeff3..b4a6810ddf9 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"668b5ba700a2318c3640d6257983caac2bb3666e37418250372ed8eb2fdd547e","body_hash":"ce68016e2d56767693325d4183f787c7a4e50de7459a0c76c93d0a05a2e4ce13","strict":true,"engine_base_url_customized":true,"agent_id":"copilot","agent_model":"o4-mini-aw","engine_versions":{"copilot":"1.0.98"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"38508eefbd952be9b4000036266a5c8aa96cca531e935b46295435dc209c749e","body_hash":"29bb33be424b364eb3f4c01913673d74ecae366716a1423a75dff081b07932dc","strict":true,"engine_base_url_customized":true,"agent_id":"copilot","agent_model":"o4-mini-aw","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","FOUNDRY_API_KEY","FOUNDRY_OPENAI_ENDPOINT","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"mcpscripts","tools":["github-discussion-query","github-issue-query","github-pr-query"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_check_run","create_discussion","create_issue","create_pull_request_review_comment","dispatch_workflow","haiku_printer","missing_data","missing_tool","noop","remove_labels","reply_to_pull_request_review_comment","report_incomplete","send_slack_message","set_issue_type","submit_pull_request_review","upload_artifact"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -205,7 +205,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: 668b5ba700a2318c3640d6257983caac2bb3666e37418250372ed8eb2fdd547e:ce68016e2d56767693325d4183f787c7a4e50de7459a0c76c93d0a05a2e4ce13 + GH_AW_HARNESS_VERSION: 38508eefbd952be9b4000036266a5c8aa96cca531e935b46295435dc209c749e:29bb33be424b364eb3f4c01913673d74ecae366716a1423a75dff081b07932dc with: script: | const path = require('path'); @@ -636,7 +636,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "668b5ba700a2318c3640d6257983caac2bb3666e37418250372ed8eb2fdd547e:ce68016e2d56767693325d4183f787c7a4e50de7459a0c76c93d0a05a2e4ce13" + GH_AW_ADMISSION_WORKFLOW_HASH: "38508eefbd952be9b4000036266a5c8aa96cca531e935b46295435dc209c749e:29bb33be424b364eb3f4c01913673d74ecae366716a1423a75dff081b07932dc" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -788,10 +788,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -925,7 +934,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/smoke-copilot-aoai-apikey.md b/.github/workflows/smoke-copilot-aoai-apikey.md index 35dbef458ea..1e52ab00338 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.md +++ b/.github/workflows/smoke-copilot-aoai-apikey.md @@ -69,7 +69,7 @@ tools: cli-proxy: true runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] upload-artifact: diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index 769306ae394..198b8dc1e16 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"312d30206eabf1a9a34ba7c00824cf8f992bf22442af74db0336f738a7f81422","body_hash":"b68dd34dd4079d94edb6f4057a0aa683e78ce777fbd29acd4386b7c49aaae289","strict":true,"engine_base_url_customized":true,"agent_id":"copilot","agent_model":"o4-mini-aw","engine_versions":{"copilot":"1.0.98"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"11972fb10f09a836138879c808a7b3e9dec8c4c47d3cb7f54ee6f6f83248e105","body_hash":"c7a33fac94b679f1c2c0e57ac7df34f3f8e0df291be37ae2ccde3ac1061dcb84","strict":true,"engine_base_url_customized":true,"agent_id":"copilot","agent_model":"o4-mini-aw","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","FOUNDRY_OPENAI_ENDPOINT","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"mcpscripts","tools":["github-discussion-query","github-issue-query","github-pr-query"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_check_run","create_discussion","create_issue","create_pull_request_review_comment","dispatch_workflow","haiku_printer","missing_data","missing_tool","noop","remove_labels","reply_to_pull_request_review_comment","report_incomplete","send_slack_message","set_issue_type","submit_pull_request_review","upload_artifact"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -208,7 +208,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: 312d30206eabf1a9a34ba7c00824cf8f992bf22442af74db0336f738a7f81422:b68dd34dd4079d94edb6f4057a0aa683e78ce777fbd29acd4386b7c49aaae289 + GH_AW_HARNESS_VERSION: 11972fb10f09a836138879c808a7b3e9dec8c4c47d3cb7f54ee6f6f83248e105:c7a33fac94b679f1c2c0e57ac7df34f3f8e0df291be37ae2ccde3ac1061dcb84 with: script: | const path = require('path'); @@ -639,7 +639,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "312d30206eabf1a9a34ba7c00824cf8f992bf22442af74db0336f738a7f81422:b68dd34dd4079d94edb6f4057a0aa683e78ce777fbd29acd4386b7c49aaae289" + GH_AW_ADMISSION_WORKFLOW_HASH: "11972fb10f09a836138879c808a7b3e9dec8c4c47d3cb7f54ee6f6f83248e105:c7a33fac94b679f1c2c0e57ac7df34f3f8e0df291be37ae2ccde3ac1061dcb84" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -797,10 +797,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -934,7 +943,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - env: GH_AW_AZURE_CLIENT_ID: adb907fd-188c-4029-b67f-2559d96b2f1b GH_AW_AZURE_TENANT_ID: 398a6654-997b-47e9-b12b-9515b896b4de diff --git a/.github/workflows/smoke-copilot-aoai-entra.md b/.github/workflows/smoke-copilot-aoai-entra.md index a5bc1172bc5..3fec63b6ea9 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.md +++ b/.github/workflows/smoke-copilot-aoai-entra.md @@ -78,7 +78,7 @@ tools: cli-proxy: true runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] upload-artifact: diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 82c0d170197..82ed96690b8 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"85eac231b933c7ff6ee9808202691b4583240ada14c79cffe8a241ea170081f0","body_hash":"06ed0435395a44eabb8bda59d7e8d54e8595258994f741bdd76ce56781a13492","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"},"agent_image_runner":"ubuntu-24.04-arm"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"aff47bc3d42c86fc088fa8bf3f3aabf64fdd918971e7af3a26d38961fa2b76b0","body_hash":"9ba7e8682a624eb934aeb9e18912c2c969d8c840c134c3779d3e0f00243fa379","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"},"agent_image_runner":"ubuntu-24.04-arm"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"has_pull_request":true,"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"mcpscripts","tools":["github-discussion-query","github-issue-query","github-pr-query"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_discussion","create_issue","create_pull_request_review_comment","dispatch_workflow","haiku_printer","missing_data","missing_tool","noop","remove_labels","report_incomplete","send_slack_message","submit_pull_request_review"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -573,7 +573,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "85eac231b933c7ff6ee9808202691b4583240ada14c79cffe8a241ea170081f0:06ed0435395a44eabb8bda59d7e8d54e8595258994f741bdd76ce56781a13492" + GH_AW_ADMISSION_WORKFLOW_HASH: "aff47bc3d42c86fc088fa8bf3f3aabf64fdd918971e7af3a26d38961fa2b76b0:9ba7e8682a624eb934aeb9e18912c2c969d8c840c134c3779d3e0f00243fa379" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -722,10 +722,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -832,7 +841,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/smoke-copilot-arm.md b/.github/workflows/smoke-copilot-arm.md index 3846cb9f47e..8b815dbc871 100644 --- a/.github/workflows/smoke-copilot-arm.md +++ b/.github/workflows/smoke-copilot-arm.md @@ -55,7 +55,7 @@ tools: playwright: runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] add-comment: diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index afb41254703..91f6a1e54a8 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9198495cab046a28283e739831528c072d4a6703d94e176262c1d34ee00fee64","body_hash":"fc5aea3c76a9631fb266b3a70f2838dbdbfdc53300b0efaf0b03a593389258ea","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5d2337c0d37a31f62e3d986dbc33d3d0f1280fc66407bc435a8549614081ee5c","body_hash":"fdccc8566101cd4191b10ecb7e4eb56a5052d6fb4b7e9121904da3cde43c5693","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.98"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","CODEX_API_KEY","COPILOT_GITHUB_TOKEN","GEMINI_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"mcpscripts","tools":["github-discussion-query","github-issue-query","github-pr-query"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_check_run","create_discussion","create_issue","create_pull_request_review_comment","dispatch_workflow","haiku_printer","missing_data","missing_tool","noop","remove_labels","reply_to_pull_request_review_comment","report_incomplete","send_slack_message","set_issue_type","submit_pull_request_review","upload_artifact"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -209,7 +209,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: 9198495cab046a28283e739831528c072d4a6703d94e176262c1d34ee00fee64:fc5aea3c76a9631fb266b3a70f2838dbdbfdc53300b0efaf0b03a593389258ea + GH_AW_HARNESS_VERSION: 5d2337c0d37a31f62e3d986dbc33d3d0f1280fc66407bc435a8549614081ee5c:fdccc8566101cd4191b10ecb7e4eb56a5052d6fb4b7e9121904da3cde43c5693 with: script: | const path = require('path'); @@ -646,7 +646,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "9198495cab046a28283e739831528c072d4a6703d94e176262c1d34ee00fee64:fc5aea3c76a9631fb266b3a70f2838dbdbfdc53300b0efaf0b03a593389258ea" + GH_AW_ADMISSION_WORKFLOW_HASH: "5d2337c0d37a31f62e3d986dbc33d3d0f1280fc66407bc435a8549614081ee5c:fdccc8566101cd4191b10ecb7e4eb56a5052d6fb4b7e9121904da3cde43c5693" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -798,10 +798,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: @@ -950,7 +959,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index 67bf0f0c20a..c963989ee9f 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -74,7 +74,7 @@ lsp: ".tsx": typescriptreact runtimes: go: - version: "1.26" + version: "1.27.3" node: version: "24" models: diff --git a/.github/workflows/smoke-cursor.lock.yml b/.github/workflows/smoke-cursor.lock.yml index 14807232002..17716119f1f 100644 --- a/.github/workflows/smoke-cursor.lock.yml +++ b/.github/workflows/smoke-cursor.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0030ab7b7018b5211aec06306e35dbd17788b00f4df32316474e90e3c4cfab0d","body_hash":"b7b5294d8cd50267169ee6a24456e953d58cebc29862f620302b43efbe796b37","strict":true,"agent_id":"cursor","agent_model":"cursor/auto"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a02c5495d4b66193bda69b922e39e919c41de961280e74c49c6b5e7494bd378a","body_hash":"b7b5294d8cd50267169ee6a24456e953d58cebc29862f620302b43efbe796b37","strict":true,"agent_id":"cursor","agent_model":"cursor/auto"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","CURSOR_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_issue","missing_data","missing_tool","noop","report_incomplete"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -552,7 +552,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "0030ab7b7018b5211aec06306e35dbd17788b00f4df32316474e90e3c4cfab0d:b7b5294d8cd50267169ee6a24456e953d58cebc29862f620302b43efbe796b37" + GH_AW_ADMISSION_WORKFLOW_HASH: "a02c5495d4b66193bda69b922e39e919c41de961280e74c49c6b5e7494bd378a:b7b5294d8cd50267169ee6a24456e953d58cebc29862f620302b43efbe796b37" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -653,10 +653,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/smoke-cursor.md b/.github/workflows/smoke-cursor.md index 8917a3a1e42..45e0017ba92 100644 --- a/.github/workflows/smoke-cursor.md +++ b/.github/workflows/smoke-cursor.md @@ -36,7 +36,7 @@ network: - go runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] add-comment: diff --git a/.github/workflows/smoke-goose.lock.yml b/.github/workflows/smoke-goose.lock.yml index a83b35fb4fd..d7f7241c214 100644 --- a/.github/workflows/smoke-goose.lock.yml +++ b/.github/workflows/smoke-goose.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1352077772ff95fb93e4dec4c58b3121a3f6a91ff31bb6f05beff190a741b632","body_hash":"2b3f948a821621fb5fbc2e96a5a59ffa4494277767a5484016343b7d45997a47","strict":true,"agent_id":"goose","agent_model":"copilot/gpt-5.4","engine_versions":{"goose":"1.54.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c1923b60a465eddc6eae54a7cf18f37a92678e10499b3e418de19757fa483b70","body_hash":"2b3f948a821621fb5fbc2e96a5a59ffa4494277767a5484016343b7d45997a47","strict":true,"agent_id":"goose","agent_model":"copilot/gpt-5.4","engine_versions":{"goose":"1.54.0"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","list_branches","list_commits","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_pull_requests","search_repositories"]},{"name":"mcpscripts","tools":["fetch-homepage"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_issue","missing_data","missing_tool","noop","report_incomplete"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -554,7 +554,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "1352077772ff95fb93e4dec4c58b3121a3f6a91ff31bb6f05beff190a741b632:2b3f948a821621fb5fbc2e96a5a59ffa4494277767a5484016343b7d45997a47" + GH_AW_ADMISSION_WORKFLOW_HASH: "c1923b60a465eddc6eae54a7cf18f37a92678e10499b3e418de19757fa483b70:2b3f948a821621fb5fbc2e96a5a59ffa4494277767a5484016343b7d45997a47" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -660,10 +660,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26.8' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: diff --git a/.github/workflows/smoke-goose.md b/.github/workflows/smoke-goose.md index 1a509b6c1c8..057767f66ff 100644 --- a/.github/workflows/smoke-goose.md +++ b/.github/workflows/smoke-goose.md @@ -39,7 +39,7 @@ network: - go runtimes: go: - version: "1.26.8" + version: "1.27.3" tools: cache-memory: true github: diff --git a/.github/workflows/smoke-kiro.lock.yml b/.github/workflows/smoke-kiro.lock.yml index 0fb973ab1d1..177679ee03b 100644 --- a/.github/workflows/smoke-kiro.lock.yml +++ b/.github/workflows/smoke-kiro.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0e5c59c675474f4c236de7d41ee0506495147e4d04c3321937eb67382d66eca3","body_hash":"9c2b0047c33b4fa48e3f4912c12c542809b5238eeaffe79187277df5b28de0b9","strict":true,"agent_id":"kiro","agent_model":"kiro/auto","engine_versions":{"kiro":"2.31.1"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4eddc4dc02d1a3c0b3c2427ad1aaf18317c7c15116af251665ff776ca796fc3f","body_hash":"9c2b0047c33b4fa48e3f4912c12c542809b5238eeaffe79187277df5b28de0b9","strict":true,"agent_id":"kiro","agent_model":"kiro/auto","engine_versions":{"kiro":"2.31.1"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","KIRO_API_KEY"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","add_labels","create_issue","missing_data","missing_tool","noop","report_incomplete"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -553,7 +553,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "0e5c59c675474f4c236de7d41ee0506495147e4d04c3321937eb67382d66eca3:9c2b0047c33b4fa48e3f4912c12c542809b5238eeaffe79187277df5b28de0b9" + GH_AW_ADMISSION_WORKFLOW_HASH: "4eddc4dc02d1a3c0b3c2427ad1aaf18317c7c15116af251665ff776ca796fc3f:9c2b0047c33b4fa48e3f4912c12c542809b5238eeaffe79187277df5b28de0b9" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -656,10 +656,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.26' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/smoke-kiro.md b/.github/workflows/smoke-kiro.md index a0bfea05cd4..d18bb0318f4 100644 --- a/.github/workflows/smoke-kiro.md +++ b/.github/workflows/smoke-kiro.md @@ -36,7 +36,7 @@ network: - go runtimes: go: - version: "1.26" + version: "1.27.3" safe-outputs: allowed-domains: [default-safe-outputs] add-comment: diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 82ba2772731..fa0f1be6946 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1ca0ee3187dfffd1c263342f787f695dddb221f356ec0544d80d6bf32d9ca5d3","body_hash":"616567422e1f95487d41852884442449c35e2ac6794a65ae2e63721e6518e7d4","strict":true,"agent_id":"pi","agent_model":"copilot/mai-code-1.1-flash","engine_versions":{"pi":"1.3.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"034ccf1eee374e7bee44794f97854cdb5a7378f94bb3519a7bfa95ee645fdff5","body_hash":"616567422e1f95487d41852884442449c35e2ac6794a65ae2e63721e6518e7d4","strict":true,"agent_id":"pi","agent_model":"copilot/mai-code-1.1-flash","engine_versions":{"pi":"1.3.0"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-java","sha":"b843ebf9b913146b38da2eaa7aeddf76074ae1ad","version":"v6.1.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"has_pull_request":true,"mcp_servers":[{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["add_comment","missing_data","missing_tool","noop","report_incomplete"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -538,7 +538,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "1ca0ee3187dfffd1c263342f787f695dddb221f356ec0544d80d6bf32d9ca5d3:616567422e1f95487d41852884442449c35e2ac6794a65ae2e63721e6518e7d4" + GH_AW_ADMISSION_WORKFLOW_HASH: "034ccf1eee374e7bee44794f97854cdb5a7378f94bb3519a7bfa95ee645fdff5:616567422e1f95487d41852884442449c35e2ac6794a65ae2e63721e6518e7d4" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -658,10 +658,19 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: - go-version: '1.24' + go-version: '1.27.3' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Setup Java uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: diff --git a/.github/workflows/smoke-test-tools.md b/.github/workflows/smoke-test-tools.md index a15b40b270a..109d9106807 100644 --- a/.github/workflows/smoke-test-tools.md +++ b/.github/workflows/smoke-test-tools.md @@ -28,7 +28,7 @@ runtimes: python: version: "3.11" go: - version: "1.24" + version: "1.27.3" java: version: "21" dotnet: diff --git a/.github/workflows/spec-extractor.lock.yml b/.github/workflows/spec-extractor.lock.yml index 545b6bd3219..3a12ea081b0 100644 --- a/.github/workflows/spec-extractor.lock.yml +++ b/.github/workflows/spec-extractor.lock.yml @@ -706,7 +706,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Collect package analysis data run: "set -e\nPACKAGES=(agentdrain cli console constants envutil fileutil gitutil logger parser repoutil semverutil sliceutil stringutil styles testutil timeutil tty types typeutil workflow)\nTOTAL=${#PACKAGES[@]}\nCACHE_DIR=/tmp/gh-aw/cache-memory/spec-extractor\nCONTEXT=/tmp/gh-aw/agent/pkg-context.md\n\n# Initialize or load rotation state\nmkdir -p \"$CACHE_DIR/extractions\"\nif [ -f \"$CACHE_DIR/rotation.json\" ]; then\n LAST_INDEX=$(python3 -c \"import json; d=json.load(open('$CACHE_DIR/rotation.json')); print(d.get('last_index', 0))\" 2>/dev/null || echo 0)\nelse\n printf '{\"last_index\":0,\"last_packages\":[],\"last_run\":\"\",\"total_packages\":%d}\\n' \"$TOTAL\" > \"$CACHE_DIR/rotation.json\"\n LAST_INDEX=0\nfi\n\n# Select next 4 packages using round-robin\nPKG0=\"${PACKAGES[$((LAST_INDEX % TOTAL))]}\"\nPKG1=\"${PACKAGES[$(((LAST_INDEX + 1) % TOTAL))]}\"\nPKG2=\"${PACKAGES[$(((LAST_INDEX + 2) % TOTAL))]}\"\nPKG3=\"${PACKAGES[$(((LAST_INDEX + 3) % TOTAL))]}\"\nNEXT_INDEX=$(((LAST_INDEX + 4) % TOTAL))\nSELECTED=(\"$PKG0\" \"$PKG1\" \"$PKG2\" \"$PKG3\")\n\necho \"Selected packages: ${SELECTED[*]} (last_index=$LAST_INDEX, next=$NEXT_INDEX)\"\n\n# Collect analysis data for each package into the context file\n{\n echo \"# Package Analysis Context\"\n echo \"\"\n echo \"**Run date**: $(date -u +%Y-%m-%d)\"\n echo \"**Rotation last_index (current)**: $LAST_INDEX\"\n echo \"**Selected packages**: ${SELECTED[*]}\"\n echo \"**Next last_index (save this after run)**: $NEXT_INDEX\"\n echo \"\"\n\n for PKG in \"${SELECTED[@]}\"; do\n echo \"---\"\n echo \"\"\n echo \"## Package: \\`$PKG\\`\"\n echo \"\"\n\n echo \"### Source Files\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f 2>/dev/null | sort || true\n echo '```'\n\n echo \"### Line Counts\"\n echo '```'\n wc -l pkg/\"$PKG\"/*.go 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Functions\"\n echo '```'\n grep -rn \"^func [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Types\"\n echo '```'\n grep -rn \"^type [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Constants\"\n echo '```'\n grep -rn \"^const [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Exported Variables\"\n echo '```'\n grep -rn \"^var [A-Z]\" \"pkg/$PKG\" --include='*.go' 2>/dev/null || true\n echo '```'\n\n echo \"### Package Doc Comments (first 30 lines per file)\"\n for f in pkg/\"$PKG\"/*.go; do\n [ -f \"$f\" ] || continue\n echo \"#### $f\"\n echo '```go'\n head -n 30 \"$f\" 2>/dev/null || true\n echo '```'\n done\n\n echo \"### Imports\"\n echo '```'\n find \"pkg/$PKG\" -name '*.go' ! -name '*_test.go' -type f -print0 | xargs -0 grep -h \"import\" 2>/dev/null | sort -u || true\n echo '```'\n\n echo \"### Existing README.md\"\n echo '```markdown'\n cat \"pkg/$PKG/README.md\" 2>/dev/null || echo \"No existing README.md\"\n echo '```'\n\n echo \"### Recent Git History (30 days)\"\n echo '```'\n git log --oneline --since='30 days ago' -- \"pkg/$PKG\" 2>/dev/null || true\n echo '```'\n echo \"\"\n done\n} > \"$CONTEXT\"\necho \"Context file written to $CONTEXT ($(wc -l < \"$CONTEXT\") lines)\"" diff --git a/.github/workflows/spec-librarian.lock.yml b/.github/workflows/spec-librarian.lock.yml index 612c3b58908..7668e4d51bd 100644 --- a/.github/workflows/spec-librarian.lock.yml +++ b/.github/workflows/spec-librarian.lock.yml @@ -667,7 +667,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/terminal-stylist.lock.yml b/.github/workflows/terminal-stylist.lock.yml index 43d51c0be2d..3dfeb990674 100644 --- a/.github/workflows/terminal-stylist.lock.yml +++ b/.github/workflows/terminal-stylist.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"61a1dbd50af0d55c904384e51afab050ae69dddb3fd4411ab8efb4b5ff2e2a79","body_hash":"87d4375544c01313f3723c097d245bb0b7f01561b91c641dcd6f6fca23d1aa77","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.6-sol","engine_versions":{"pi":"1.3.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e313f0705a82a12a754e7048888e7ead4b5bf8d047624fd1a3d7a0ac3dc601b4","body_hash":"d951b0896bc59d8b3d0a2577423783b13163ec4f0b138d48f20bb518b10dd899","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.6-sol","engine_versions":{"pi":"1.3.0"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62","digest":"sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62","digest":"sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62","digest":"sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62","digest":"sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.35","digest":"sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f","pinned_image":"ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"},{"image":"ghcr.io/oraios/serena:1.7.0","digest":"sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730","pinned_image":"ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730"}],"mcp_servers":[{"name":"safeoutputs","tools":["create_discussion","missing_data","missing_tool","noop","report_incomplete"]},{"name":"serena","tools":["*"]}],"threat_detection":{"mode":"enabled"}} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -461,7 +461,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "61a1dbd50af0d55c904384e51afab050ae69dddb3fd4411ab8efb4b5ff2e2a79:87d4375544c01313f3723c097d245bb0b7f01561b91c641dcd6f6fca23d1aa77" + GH_AW_ADMISSION_WORKFLOW_HASH: "e313f0705a82a12a754e7048888e7ead4b5bf8d047624fd1a3d7a0ac3dc601b4:d951b0896bc59d8b3d0a2577423783b13163ec4f0b138d48f20bb518b10dd899" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -653,7 +653,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/cli-proxy:0.28.62@sha256:b23bbe2c025f60ea9aacde67c078ea937b54b4039552bca072ae9610d9e1c294 ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index 20706c58d6f..268822aebf3 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -703,7 +703,7 @@ jobs: cache: true go-version-file: go.mod - name: Expose Go to Serena container - run: "set -euo pipefail\ngo_root=\"$(go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" + run: "set -euo pipefail\nunset GOROOT\ngo_root=\"$(GOTOOLCHAIN=local go env GOROOT)\"\ncase \"$go_root\" in\n \"${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}\"/go/*) ;;\n *)\n echo \"Serena Go runtime is outside its read-only runner tool-cache mount; check actions/setup-go and RUNNER_TOOL_CACHE.\" >&2\n exit 1\n ;;\nesac\nif [ ! -x \"$go_root/bin/go\" ]; then\n echo \"Serena Go runtime has no executable bin/go in the mounted toolchain.\" >&2\n exit 1\nfi\nexport GOROOT=\"$go_root\"\nprintf 'GOROOT=%s\\n' \"$go_root\" >> \"$GITHUB_ENV\"\nprintf 'GH_AW_SERENA_GO_PATH=%s/bin:%s\\n' \"$go_root\" \"$PATH\" >> \"$GITHUB_ENV\"\n\"$go_root/bin/go\" version\n" - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.62@sha256:80c4ddd927647b636f49807d2557b4cd3ec175c894194e432c9374f5a69e890f ghcr.io/github/gh-aw-firewall/api-proxy:0.28.62@sha256:ebb9e7b5bec475f6b6c2eb6e999ada7d41d57d0acbacb8493c5bebc0106fd44d ghcr.io/github/gh-aw-firewall/squid:0.28.62@sha256:b145850ed8bdffd2d4fcd8f1f3ea5793b56cadee6989700b91f42cff490630ef ghcr.io/github/gh-aw-mcpg:v0.4.35@sha256:35e28671836380c0eae52660ea2ad7e5f6659f63cb20d1abae9271656c670858 ghcr.io/github/gh-aw-node@sha256:11c2c544876cdbaffefe0f7f1283b74838c209654b4934a78e23ed11f750490f ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796 ghcr.io/oraios/serena:1.7.0@sha256:6c9459e4246a39c9deaa4f23fb05a526ac6e237b24c8e84a927a098fa1ab6730 diff --git a/.golangci.yml b/.golangci.yml index 59adb18d6f0..17e06a2bf1d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ version: "2" run: - go: "1.26" + go: "1.27.3" tests: true timeout: 5m # Prevent linter from hanging indefinitely modules-download-mode: readonly # Use cached modules only, skip downloads diff --git a/CHANGELOG.md b/CHANGELOG.md index 97cb8ff7a1f..721be022406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,10 @@ Copilot CLI v1.0.98, and the legacy Copilot v1.0.21 manifest. Obtain other manifest digests through an independently trusted channel and update/recompile workflows to consume the new setup action. Explicit Copilot pins bypass toolcache selection; without a supplied pin, trusted toolcache behavior is -unchanged. Go release builds require -1.26.10, 1.27.3, or a newer supported stable toolchain. +unchanged. Repository Go tooling, module requirements and generated runtime +defaults now use Go 1.27.3; release builds reject older toolchains. Go root +capture and Serena setup clear stale GOROOT values before discovering the +selected toolchain, preventing mixed-version compiler/standard-library failures. Logs episode rollups now require a recorded nonempty `context.episode_id`. Call-ID grouping, synthetic episode IDs, metadata-inferred lineage, and bridges diff --git a/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md index 8507cb3449c..ab6ea31e02c 100644 --- a/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md +++ b/docs/adr/70549-authenticate-bootstrap-downloads-with-bundled-release-pins.md @@ -14,7 +14,7 @@ The incident motivating this change is corroborated by [Mozilla's root-program a ### Decision -The proposed decision is to authenticate bootstrap artifacts against SHA256 digests bound to specific releases and supplied independently of network-served checksums. A new `actions/setup/sh/release_integrity.sh` holds a reviewed pin table with entries of the form `(repository, version, asset, sha256)`. AWF and Copilot CLI installers must verify their downloaded checksum manifest against this table or a literal workflow `checksums-sha256` override before using it. Overrides belong to `sandbox.agent` for AWF and `engine` for Copilot, require an explicit semantic release version, and propagate to matching detection/eval installations. Explicit Copilot pins bypass the toolcache and verify both host and requested musl archives. Docker Compose bytes and the GitHub CLI apt signing key are pinned directly; the generated Compose step must keep its verifier inside the trusted compiled step rather than sourcing a helper from a writable runtime directory. If a release has neither a built-in nor a supplied pin, the download fails closed; `latest` cannot be paired with a supplied pin. An AWF bundle that fails verification no longer falls back to the standalone binary. The hardened AWF, Copilot CLI, generated Docker Compose, and GitHub CLI installer `curl` calls are limited to HTTPS for both the initial request and any redirects (`--proto '=https' --proto-redir '=https'`); this is not a guarantee for every bootstrap download path. Release builds require Go 1.26.10 or newer on the 1.26 branch, 1.27.3 or newer on the 1.27 branch, or a newer supported stable release; each native binary's embedded toolchain is recorded. Together, these steps separate destination authorization, transport authentication, and artifact integrity as distinct security boundaries. +The proposed decision is to authenticate bootstrap artifacts against SHA256 digests bound to specific releases and supplied independently of network-served checksums. A new `actions/setup/sh/release_integrity.sh` holds a reviewed pin table with entries of the form `(repository, version, asset, sha256)`. AWF and Copilot CLI installers must verify their downloaded checksum manifest against this table or a literal workflow `checksums-sha256` override before using it. Overrides belong to `sandbox.agent` for AWF and `engine` for Copilot, require an explicit semantic release version, and propagate to matching detection/eval installations. Explicit Copilot pins bypass the toolcache and verify both host and requested musl archives. Docker Compose bytes and the GitHub CLI apt signing key are pinned directly; the generated Compose step must keep its verifier inside the trusted compiled step rather than sourcing a helper from a writable runtime directory. If a release has neither a built-in nor a supplied pin, the download fails closed; `latest` cannot be paired with a supplied pin. An AWF bundle that fails verification no longer falls back to the standalone binary. The hardened AWF, Copilot CLI, generated Docker Compose, and GitHub CLI installer `curl` calls are limited to HTTPS for both the initial request and any redirects (`--proto '=https' --proto-redir '=https'`); this is not a guarantee for every bootstrap download path. Repository modules, lint tooling, the devcontainer and owned workflow Go runtimes use Go 1.27.3; release builds reject older toolchains; each native binary's embedded toolchain is recorded. Together, these steps separate destination authorization, transport authentication, and artifact integrity as distinct security boundaries. ### Alternatives Considered @@ -44,7 +44,7 @@ Embed per-architecture artifact digests in compiled lock files, as threat-detect - **Breaking change**: AWF/Copilot downloads without a built-in or supplied pin and uncached `latest` downloads now fail. Users must select a reviewed built-in release or supply an explicit version and independently trusted checksum-manifest pin. - New built-in releases need maintainer-reviewed pin updates in `release_integrity.sh`. Workflow authors can select other releases without waiting for that update, but must obtain their manifest digests through an independently authenticated channel. - The trust anchor moves to the setup action and the compiler. A compromised setup action, runner image, or toolcache is out of scope and is not mitigated. -- Requiring a newer Go version (module go directive 1.26.10 and a preferred 1.27.3 toolchain) may break contributors or build environments using older toolchains. +- Requiring Go 1.27.3 for repository modules and tooling may break contributors or build environments using older toolchains. #### Neutral - Container images, MCP servers, Node/Python clients, and proxy trust stores are not patched or attested. `runtime_trust_inventory.sh` only logs diagnostic version evidence. diff --git a/go.mod b/go.mod index a782a5c79a0..2b2939e2370 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/github/gh-aw -go 1.26.10 - -toolchain go1.27.3 +go 1.27.3 require ( charm.land/bubbles/v2 v2.2.1 diff --git a/pkg/cli/logs_episode_retrieval_artifacts.go b/pkg/cli/logs_episode_retrieval_artifacts.go index 0e4ef3e05c5..6ffec0f6234 100644 --- a/pkg/cli/logs_episode_retrieval_artifacts.go +++ b/pkg/cli/logs_episode_retrieval_artifacts.go @@ -301,8 +301,7 @@ func selectEpisodeInfo(infos []*AwInfo) (*AwInfo, error) { } func processEpisodeArtifacts(run WorkflowRun, runDir string, info *AwInfo) (ProcessedRun, error) { - result := DownloadResult{} - result.Run = run + result := DownloadResult{Run: run} result.Run.LogsPath = runDir metrics := extractRunMetricsAndMetadata(&result, runDir, false) usage, err := analyzeTokenUsage(runDir, false) diff --git a/pkg/cli/outcome_eval_agent.go b/pkg/cli/outcome_eval_agent.go index 8468edfccf3..cbffe71a422 100644 --- a/pkg/cli/outcome_eval_agent.go +++ b/pkg/cli/outcome_eval_agent.go @@ -14,8 +14,10 @@ import ( func evalAssignToAgent(ctx context.Context, item CreatedItemReport, repoOverride string) OutcomeReport { repo := resolveItemRepo(item, repoOverride) num := resolveItemNumber(item) - report := OutcomeReport{Type: item.Type, ObjectURL: item.URL, ObjectNumber: num, Repo: repo} - report.OutcomeEvaluation = outcomeEvidence(OutcomeStatusUnknown, EvidenceNone, "missing_reference") + report := OutcomeReport{ + Type: item.Type, ObjectURL: item.URL, ObjectNumber: num, Repo: repo, + OutcomeEvaluation: outcomeEvidence(OutcomeStatusUnknown, EvidenceNone, "missing_reference"), + } if num == 0 || repo == "" { return report } diff --git a/pkg/cli/token_usage_subagent_session.go b/pkg/cli/token_usage_subagent_session.go index eff731c199b..855b5da4dd7 100644 --- a/pkg/cli/token_usage_subagent_session.go +++ b/pkg/cli/token_usage_subagent_session.go @@ -883,8 +883,8 @@ func (models *subagentSessionModels) addAgentMetricModelUsage(agentID string, me return errors.New("negative agent request count") } counts[model] = requestCount - actual := SubagentModelActual{Model: model, Requests: requestCount} - actual.TokenCoreMetrics = TokenCoreMetrics{ + actual := SubagentModelActual{ + Model: model, Requests: requestCount, InputTokens: usage.Usage.InputTokens, OutputTokens: usage.Usage.OutputTokens, CacheReadTokens: usage.Usage.CacheReadTokens, CacheWriteTokens: usage.Usage.CacheWriteTokens, ReasoningTokens: usage.Usage.ReasoningTokens, diff --git a/pkg/constants/version_constants.go b/pkg/constants/version_constants.go index 8e6adb939d2..c7f4521234f 100644 --- a/pkg/constants/version_constants.go +++ b/pkg/constants/version_constants.go @@ -349,7 +349,7 @@ const DefaultJavaVersion Version = "21" const DefaultElixirVersion Version = "1.17" // DefaultGoVersion is the default version of Go for runtime setup -const DefaultGoVersion Version = "1.26.10" +const DefaultGoVersion Version = "1.27.3" // DefaultHaskellVersion is the default version of GHC for runtime setup const DefaultHaskellVersion Version = "9.10" diff --git a/pkg/workflow/agy_engine.go b/pkg/workflow/agy_engine.go index 5f9fb1a5e95..299ca12bf3f 100644 --- a/pkg/workflow/agy_engine.go +++ b/pkg/workflow/agy_engine.go @@ -19,16 +19,14 @@ var _ MCPConfigAdapterProvider = (*AgyEngine)(nil) func NewAgyEngine() *AgyEngine { return &AgyEngine{ - BaseEngine: BaseEngine{ - id: string(constants.AgyEngine), - displayName: "Google Antigravity CLI", - description: "Experimental native Agy CLI with Gemini API-key authentication", - experimental: true, - capabilities: EngineCapabilities{ - MCP: true, - }, - dedicatedLLMGatewayPort: constants.GeminiLLMGatewayPort, + id: string(constants.AgyEngine), + displayName: "Google Antigravity CLI", + description: "Experimental native Agy CLI with Gemini API-key authentication", + experimental: true, + capabilities: EngineCapabilities{ + MCP: true, }, + dedicatedLLMGatewayPort: constants.GeminiLLMGatewayPort, } } diff --git a/pkg/workflow/assign_to_user.go b/pkg/workflow/assign_to_user.go index a9e951ba685..b97937fbedd 100644 --- a/pkg/workflow/assign_to_user.go +++ b/pkg/workflow/assign_to_user.go @@ -43,7 +43,7 @@ func (c *Compiler) parseAssignToUserConfig(outputMap map[string]any) *AssignToUs // For backward compatibility, use defaults assignToUserLog.Print("Using default configuration") return &AssignToUserConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: defaultIntStr(1)}, + Max: defaultIntStr(1), } }, func(config *AssignToUserConfig) { diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index 53b2beb5253..30c7a0ac82b 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -61,17 +61,13 @@ func NewBehaviorDefinedEngine(def *EngineDefinition) (*BehaviorDefinedEngine, er } engine := &BehaviorDefinedEngine{ - UniversalLLMConsumerEngine: UniversalLLMConsumerEngine{ - BaseEngine: BaseEngine{ - id: def.ID, - displayName: def.DisplayName, - description: def.Description, - experimental: def.Experimental, - ghSkillAgentName: def.GHSkillAgentName, - capabilities: capabilities, - }, - }, - definition: def, + id: def.ID, + displayName: def.DisplayName, + description: def.Description, + experimental: def.Experimental, + ghSkillAgentName: def.GHSkillAgentName, + capabilities: capabilities, + definition: def, } return engine, nil } diff --git a/pkg/workflow/config_parsing_helpers_test.go b/pkg/workflow/config_parsing_helpers_test.go index 25165420380..f09ad35ed0e 100644 --- a/pkg/workflow/config_parsing_helpers_test.go +++ b/pkg/workflow/config_parsing_helpers_test.go @@ -847,7 +847,7 @@ func TestUnmarshalConfig(t *testing.T) { t.Errorf("expected expires 7, got %d", config.Expires) } if templatableIntValue(config.Max) != 5 { - t.Errorf("expected max 5, got %d", config.Max) + t.Errorf("expected max 5, got %v", config.Max) } if config.GitHubToken != "${{ secrets.TOKEN }}" { t.Errorf("expected github-token, got %q", config.GitHubToken) @@ -886,7 +886,7 @@ func TestUnmarshalConfig(t *testing.T) { t.Errorf("expected title-prefix '[auto] ', got %q", config.TitlePrefix) } if templatableIntValue(config.Max) != 3 { - t.Errorf("expected max 3, got %d", config.Max) + t.Errorf("expected max 3, got %v", config.Max) } // Other fields should be zero values if len(config.Labels) != 0 { @@ -912,7 +912,7 @@ func TestUnmarshalConfig(t *testing.T) { validate: func(t *testing.T, config *CreateIssuesConfig) { // All fields should be zero values if templatableIntValue(config.Max) != 0 { - t.Errorf("expected max 0, got %d", config.Max) + t.Errorf("expected max 0, got %v", config.Max) } }, }, diff --git a/pkg/workflow/create_project.go b/pkg/workflow/create_project.go index 9de9856d4eb..4db6e1a7548 100644 --- a/pkg/workflow/create_project.go +++ b/pkg/workflow/create_project.go @@ -47,7 +47,7 @@ func (c *Compiler) parseCreateProjectsConfig(outputMap map[string]any) *CreatePr createProjectsConfig.FieldDefinitions = parseProjectFieldDefinitions(configMap, createProjectLog) } - createProjectLog.Printf("Parsed create-project config: max=%d, hasCustomToken=%v, hasTargetOwner=%v, hasTitlePrefix=%v, viewCount=%d, fieldDefinitionCount=%d", + createProjectLog.Printf("Parsed create-project config: max=%v, hasCustomToken=%v, hasTargetOwner=%v, hasTitlePrefix=%v, viewCount=%d, fieldDefinitionCount=%d", createProjectsConfig.Max, createProjectsConfig.GitHubToken != "", createProjectsConfig.TargetOwner != "", createProjectsConfig.TitlePrefix != "", len(createProjectsConfig.Views), len(createProjectsConfig.FieldDefinitions)) return createProjectsConfig } diff --git a/pkg/workflow/create_project_status_update.go b/pkg/workflow/create_project_status_update.go index 3d4bff7f075..97452acab74 100644 --- a/pkg/workflow/create_project_status_update.go +++ b/pkg/workflow/create_project_status_update.go @@ -31,7 +31,7 @@ func (c *Compiler) parseCreateProjectStatusUpdateConfig(outputMap map[string]any } } - createProjectStatusUpdateLog.Printf("Parsed create-project-status-update config: max=%d, hasCustomToken=%v, hasCustomProject=%v", + createProjectStatusUpdateLog.Printf("Parsed create-project-status-update config: max=%v, hasCustomToken=%v, hasCustomProject=%v", config.Max, config.GitHubToken != "", config.Project != "") return config } diff --git a/pkg/workflow/dependabot_manifests.go b/pkg/workflow/dependabot_manifests.go index f8ee66312f9..112d74acec9 100644 --- a/pkg/workflow/dependabot_manifests.go +++ b/pkg/workflow/dependabot_manifests.go @@ -516,7 +516,7 @@ func (c *Compiler) loadOrInitGoModLines(path string) ([]string, error) { dependabotLog.Print("Creating new go.mod") lines = append(lines, "module github.com/github/gh-aw-workflows-deps") lines = append(lines, "") - lines = append(lines, "go 1.21") + lines = append(lines, "go "+string(constants.DefaultGoVersion)) } return lines, nil diff --git a/pkg/workflow/hide_comment.go b/pkg/workflow/hide_comment.go index 0e524e2eece..6668ca06a4c 100644 --- a/pkg/workflow/hide_comment.go +++ b/pkg/workflow/hide_comment.go @@ -47,7 +47,7 @@ func (c *Compiler) parseHideCommentConfig(outputMap map[string]any) *HideComment // Parse common base fields with default max of 5 c.parseBaseSafeOutputConfig(configMap, &hideCommentConfig.BaseSafeOutputConfig, 5) - hideCommentLog.Printf("Parsed hide-comment config: max=%d, target_repo=%s", + hideCommentLog.Printf("Parsed hide-comment config: max=%v, target_repo=%s", hideCommentConfig.Max, hideCommentConfig.TargetRepoSlug) } else { // If configData is nil or not a map, still set the default max diff --git a/pkg/workflow/link_sub_issue.go b/pkg/workflow/link_sub_issue.go index 65573db8fd3..185d09cf2d3 100644 --- a/pkg/workflow/link_sub_issue.go +++ b/pkg/workflow/link_sub_issue.go @@ -49,7 +49,7 @@ func (c *Compiler) parseLinkSubIssueConfig(outputMap map[string]any) *LinkSubIss // Parse sub-title-prefix linkSubIssueConfig.SubTitlePrefix = extractStringFromMap(configMap, "sub-title-prefix", linkSubIssueLog) - linkSubIssueLog.Printf("Parsed link-sub-issue config: max=%d, parent_labels=%d, sub_labels=%d, target_repo=%s", + linkSubIssueLog.Printf("Parsed link-sub-issue config: max=%v, parent_labels=%d, sub_labels=%d, target_repo=%s", linkSubIssueConfig.Max, len(linkSubIssueConfig.ParentRequiredLabels), len(linkSubIssueConfig.SubRequiredLabels), linkSubIssueConfig.TargetRepoSlug) } else { diff --git a/pkg/workflow/reply_to_pr_review_comment.go b/pkg/workflow/reply_to_pr_review_comment.go index 54cb1e75087..e7938d1e64b 100644 --- a/pkg/workflow/reply_to_pr_review_comment.go +++ b/pkg/workflow/reply_to_pr_review_comment.go @@ -61,7 +61,7 @@ func (c *Compiler) parseReplyToPullRequestReviewCommentConfig(outputMap map[stri config.Footer = &footer } - replyToPRReviewCommentLog.Printf("Parsed reply-to-pull-request-review-comment config: max=%d", config.Max) + replyToPRReviewCommentLog.Printf("Parsed reply-to-pull-request-review-comment config: max=%v", config.Max) } else { // If configData is nil or not a map, still set the default max config.Max = defaultIntStr(10) diff --git a/pkg/workflow/runtime_go_baseline_test.go b/pkg/workflow/runtime_go_baseline_test.go new file mode 100644 index 00000000000..dd67786b8f2 --- /dev/null +++ b/pkg/workflow/runtime_go_baseline_test.go @@ -0,0 +1,81 @@ +//go:build !integration + +package workflow + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/github/gh-aw/pkg/constants" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" +) + +func TestRepositoryGoToolingBaseline(t *testing.T) { + t.Parallel() + require.Equal(t, "1.27.3", string(constants.DefaultGoVersion)) + root := filepath.Join("..", "..") + for _, path := range []string{"go.mod", ".github/workflows/go.mod"} { + data, err := os.ReadFile(filepath.Join(root, path)) + require.NoError(t, err) + require.Contains(t, "\n"+string(data), "\ngo 1.27.3\n", path) + } + data, err := os.ReadFile(filepath.Join(root, ".golangci.yml")) + require.NoError(t, err) + var lint struct { + Run struct { + Go string `yaml:"go"` + } `yaml:"run"` + } + require.NoError(t, yaml.Unmarshal(data, &lint)) + require.Equal(t, "1.27.3", lint.Run.Go) + data, err = os.ReadFile(filepath.Join(root, ".devcontainer", "devcontainer.json")) + require.NoError(t, err) + var devcontainer struct { + Features map[string]struct { + Version string `json:"version"` + } `json:"features"` + } + require.NoError(t, json.Unmarshal(data, &devcontainer)) + require.Equal(t, "1.27.3", devcontainer.Features["ghcr.io/devcontainers/features/go:1"].Version) +} + +func TestOwnedWorkflowGoVersionPins(t *testing.T) { + t.Parallel() + paths, err := filepath.Glob(filepath.Join("..", "..", ".github", "workflows", "*.md")) + require.NoError(t, err) + checked := 0 + for _, path := range paths { + data, err := os.ReadFile(path) + require.NoError(t, err) + parts := strings.SplitN(string(data), "---", 3) + if len(parts) != 3 || strings.TrimSpace(parts[0]) != "" { + continue + } + var config struct { + Source string `yaml:"source"` + Runtimes struct { + Go struct { + Version string `yaml:"version"` + } `yaml:"go"` + } `yaml:"runtimes"` + } + require.NoError(t, yaml.Unmarshal([]byte(parts[1]), &config), path) + if config.Source != "" || config.Runtimes.Go.Version == "" { + continue + } + require.Equal(t, "1.27.3", config.Runtimes.Go.Version, path) + checked++ + } + require.Positive(t, checked, "owned runtime pins must actually be inspected") +} + +func TestNewWorkflowDependencyModuleUsesGoBaseline(t *testing.T) { + t.Parallel() + lines, err := NewCompiler().loadOrInitGoModLines(filepath.Join(t.TempDir(), "go.mod")) + require.NoError(t, err) + require.Contains(t, lines, "go "+string(constants.DefaultGoVersion)) +} diff --git a/pkg/workflow/runtime_go_root_test.go b/pkg/workflow/runtime_go_root_test.go new file mode 100644 index 00000000000..94a5f9d14ec --- /dev/null +++ b/pkg/workflow/runtime_go_root_test.go @@ -0,0 +1,56 @@ +//go:build !integration + +package workflow + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGoRootCaptureReplacesStaleToolchainRoot(t *testing.T) { + t.Parallel() + for _, scenario := range []string{"selected", "query-failure", "missing-binary"} { + t.Run(scenario, func(t *testing.T) { + dir := t.TempDir() + bin := filepath.Join(dir, "bin") + root := filepath.Join(dir, "go toolchain", "1.27.3") + require.NoError(t, os.MkdirAll(bin, 0o755)) + require.NoError(t, os.MkdirAll(filepath.Join(root, "bin"), 0o755)) + mock := `#!/bin/sh +[ -z "${GOROOT:-}" ] && [ "$GOTOOLCHAIN" = local ] || exit 42 +if [ "$SCENARIO" = query-failure ]; then exit 1; fi +printf '%s\n' "$SELECTED_GO_ROOT" +` + require.NoError(t, os.WriteFile(filepath.Join(bin, "go"), []byte(mock), 0o755)) + if scenario != "missing-binary" { + require.NoError(t, os.WriteFile(filepath.Join(root, "bin", "go"), []byte(mock), 0o755)) + } + envFile := filepath.Join(dir, "github-env") + step := generateGoRootCaptureStep() + require.Contains(t, strings.Join(step, "\n"), "shell: bash") + var script strings.Builder + for _, line := range step[3:] { + script.WriteString(strings.TrimPrefix(line, " ") + "\n") + } + cmd := exec.Command("bash", "-c", script.String()) + cmd.Env = append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), + "GOROOT="+filepath.Join(dir, "stale-go"), "GOTOOLCHAIN=auto", + "SELECTED_GO_ROOT="+root, "SCENARIO="+scenario, "GITHUB_ENV="+envFile) + output, err := cmd.CombinedOutput() + if scenario == "selected" { + require.NoError(t, err, "%s", output) + exported, err := os.ReadFile(envFile) + require.NoError(t, err) + require.Equal(t, "GOROOT="+root+"\n", string(exported)) + } else { + require.Error(t, err, "%s", output) + require.NoFileExists(t, envFile, "failed discovery must not export an empty or stale root") + } + }) + } +} diff --git a/pkg/workflow/runtime_setup_test.go b/pkg/workflow/runtime_setup_test.go index 15161df04fb..d1103ff24f1 100644 --- a/pkg/workflow/runtime_setup_test.go +++ b/pkg/workflow/runtime_setup_test.go @@ -460,7 +460,7 @@ func TestGenerateRuntimeSetupSteps(t *testing.T) { checkContent: []string{ "Setup Go", "actions/setup-go@", - "go-version: '1.26.10'", + "go-version: '1.27.3'", "cache: false", "Capture GOROOT for AWF chroot mode", }, diff --git a/pkg/workflow/runtime_step_generator.go b/pkg/workflow/runtime_step_generator.go index 4ee0b956902..13e7bb3888f 100644 --- a/pkg/workflow/runtime_step_generator.go +++ b/pkg/workflow/runtime_step_generator.go @@ -33,7 +33,7 @@ func GenerateRuntimeSetupSteps(requirements []RuntimeRequirement, data *Workflow // for /proc/self/exe resolution. actions/setup-go does NOT export GOROOT to the // environment, so we must capture it explicitly. runtimeStepGeneratorLog.Print("Adding GOROOT capture step for chroot mode compatibility") - steps = append(steps, generateEnvCaptureStep("GOROOT", "go env GOROOT")) + steps = append(steps, generateGoRootCaptureStep()) case "elixir": // erlef/setup-beam installs OTP to ${RUNNER_TEMP}/.setup-beam/otp/ via core.addPath(), // not to RUNNER_TOOL_CACHE. GetNpmBinPathSetup() only scans RUNNER_TOOL_CACHE, so @@ -52,6 +52,22 @@ func GenerateRuntimeSetupSteps(requirements []RuntimeRequirement, data *Workflow return steps } +func generateGoRootCaptureStep() GitHubActionStep { + return GitHubActionStep{ + " - name: Capture GOROOT for AWF chroot mode", + " shell: bash", + " run: |", + " set -euo pipefail", + " unset GOROOT", + ` go_root="$(GOTOOLCHAIN=local go env GOROOT)"`, + ` if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then`, + ` echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2`, + " exit 1", + " fi", + ` printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV"`, + } +} + // generateEnvCaptureStep creates a step to capture an environment variable and export it. // This is required because some setup actions don't export env vars, but AWF chroot mode // needs them to be set in the environment to pass them to the container. diff --git a/pkg/workflow/serena_go_runtime_test.go b/pkg/workflow/serena_go_runtime_test.go index 8a3e6e97a0f..958606cdf4f 100644 --- a/pkg/workflow/serena_go_runtime_test.go +++ b/pkg/workflow/serena_go_runtime_test.go @@ -78,7 +78,7 @@ func testSerenaGoExposureScript(t *testing.T, script, serverPath string) { t.Run(scenario, func(t *testing.T) { dir := t.TempDir() cache := filepath.Join(dir, "tool cache") - root := filepath.Join(cache, "go", "1.26.9", "x64") + root := filepath.Join(cache, "go", "1.27.3", "x64") if scenario == "outside-mount" { root = filepath.Join(dir, "outside") } @@ -87,7 +87,7 @@ func testSerenaGoExposureScript(t *testing.T, script, serverPath string) { require.NoError(t, os.MkdirAll(mockBin, 0o700)) require.NoError(t, os.MkdirAll(nodeBin, 0o700)) require.NoError(t, os.MkdirAll(filepath.Join(root, "bin"), 0o700)) - mock := "#!/bin/sh\nif [ \"$1\" = env ] && [ \"$2\" = GOROOT ]; then printf '%s\\n' \"$TEST_GO_ROOT\"; else printf 'go version mocked\\n'; fi\n" + mock := "#!/bin/sh\nif [ \"$1\" = env ] && [ \"$2\" = GOROOT ]; then\n [ -z \"${GOROOT:-}\" ] && [ \"$GOTOOLCHAIN\" = local ] || exit 1\n printf '%s\\n' \"$TEST_GO_ROOT\"\nelse\n [ \"$GOROOT\" = \"$TEST_GO_ROOT\" ] || exit 1\n printf 'go version mocked\\n'\nfi\n" require.NoError(t, os.WriteFile(filepath.Join(mockBin, "go"), []byte(mock), 0o700)) require.NoError(t, os.WriteFile(filepath.Join(nodeBin, "node"), []byte("#!/bin/sh\nprintf 'node mocked\\n'\n"), 0o700)) if scenario != "missing-binary" { @@ -97,15 +97,15 @@ func testSerenaGoExposureScript(t *testing.T, script, serverPath string) { require.NoError(t, os.WriteFile(envFile, nil, 0o600)) cmd := exec.Command("bash", "-c", script) runnerPath := mockBin + ":" + nodeBin + ":/usr/bin:/bin" - cmd.Env = []string{"PATH=" + runnerPath, "TEST_GO_ROOT=" + root, "RUNNER_TOOL_CACHE=" + cache, "GITHUB_ENV=" + envFile} + cmd.Env = []string{"PATH=" + runnerPath, "TEST_GO_ROOT=" + root, "RUNNER_TOOL_CACHE=" + cache, "GITHUB_ENV=" + envFile, "GOROOT=" + filepath.Join(cache, "go", "1.26.9", "x64")} output, err := cmd.CombinedOutput() exported, readErr := os.ReadFile(envFile) require.NoError(t, readErr) if scenario == "mounted" { require.NoError(t, err, "%s", output) - require.Equal(t, "GH_AW_SERENA_GO_PATH="+root+"/bin:"+runnerPath+"\n", string(exported)) + require.Equal(t, "GOROOT="+root+"\nGH_AW_SERENA_GO_PATH="+root+"/bin:"+runnerPath+"\n", string(exported)) // Model the gateway's variable expansion with its own PATH, not the runner's. - exposedPath := strings.TrimSuffix(strings.TrimPrefix(string(exported), "GH_AW_SERENA_GO_PATH="), "\n") + exposedPath := strings.TrimSuffix(strings.TrimPrefix(strings.SplitN(string(exported), "\n", 2)[1], "GH_AW_SERENA_GO_PATH="), "\n") resolvedPath := os.Expand(strings.ReplaceAll(serverPath, `\$`, `$`), func(name string) string { switch name { case "GH_AW_SERENA_GO_PATH": diff --git a/pkg/workflow/update_discussion_test.go b/pkg/workflow/update_discussion_test.go index 7b73d542969..b293ac02ad8 100644 --- a/pkg/workflow/update_discussion_test.go +++ b/pkg/workflow/update_discussion_test.go @@ -57,7 +57,7 @@ This workflow tests the update-discussion configuration parsing. // Check defaults if templatableIntValue(workflowData.SafeOutputs.UpdateDiscussions.Max) != 1 { - t.Fatalf("Expected max to be 1, got %d", workflowData.SafeOutputs.UpdateDiscussions.Max) + t.Fatalf("Expected max to be 1, got %v", workflowData.SafeOutputs.UpdateDiscussions.Max) } if workflowData.SafeOutputs.UpdateDiscussions.Target != "" { @@ -134,7 +134,7 @@ This workflow tests the update-discussion configuration with all options. // Check all options if templatableIntValue(workflowData.SafeOutputs.UpdateDiscussions.Max) != 3 { - t.Fatalf("Expected max to be 3, got %d", workflowData.SafeOutputs.UpdateDiscussions.Max) + t.Fatalf("Expected max to be 3, got %v", workflowData.SafeOutputs.UpdateDiscussions.Max) } if workflowData.SafeOutputs.UpdateDiscussions.Target != "*" { diff --git a/pkg/workflow/update_entity_helpers_test.go b/pkg/workflow/update_entity_helpers_test.go index 756871cdb5c..7af6bb908a3 100644 --- a/pkg/workflow/update_entity_helpers_test.go +++ b/pkg/workflow/update_entity_helpers_test.go @@ -167,7 +167,7 @@ func TestParseUpdateEntityConfigWithFields(t *testing.T) { wantNil: false, validateFunc: func(t *testing.T, cfg *UpdateEntityConfig) { if templatableIntValue(cfg.Max) != 2 { - t.Errorf("Expected max=2, got %d", cfg.Max) + t.Errorf("Expected max=2, got %v", cfg.Max) } }, }, @@ -195,7 +195,7 @@ func TestParseUpdateEntityConfigWithFields(t *testing.T) { wantNil: false, validateFunc: func(t *testing.T, cfg *UpdateEntityConfig) { if templatableIntValue(cfg.Max) != 3 { - t.Errorf("Expected max=3, got %d", cfg.Max) + t.Errorf("Expected max=3, got %v", cfg.Max) } }, }, @@ -220,7 +220,7 @@ func TestParseUpdateEntityConfigWithFields(t *testing.T) { wantNil: false, validateFunc: func(t *testing.T, cfg *UpdateEntityConfig) { if templatableIntValue(cfg.Max) != 1 { - t.Errorf("Expected max=1, got %d", cfg.Max) + t.Errorf("Expected max=1, got %v", cfg.Max) } }, }, @@ -284,7 +284,7 @@ func TestParseUpdateEntityConfigTyped(t *testing.T) { wantNil: false, validateFunc: func(t *testing.T, cfg *UpdateIssuesConfig) { if templatableIntValue(cfg.Max) != 2 { - t.Errorf("Expected max=2, got %d", cfg.Max) + t.Errorf("Expected max=2, got %v", cfg.Max) } if cfg.Title == nil { t.Error("Expected title to be non-nil") diff --git a/pkg/workflow/update_issue_test.go b/pkg/workflow/update_issue_test.go index 2c414e42144..2a24df966be 100644 --- a/pkg/workflow/update_issue_test.go +++ b/pkg/workflow/update_issue_test.go @@ -58,7 +58,7 @@ This workflow tests the update-issue configuration parsing. // Check defaults if templatableIntValue(workflowData.SafeOutputs.UpdateIssues.Max) != 1 { - t.Fatalf("Expected max to be 1, got %d", workflowData.SafeOutputs.UpdateIssues.Max) + t.Fatalf("Expected max to be 1, got %v", workflowData.SafeOutputs.UpdateIssues.Max) } if workflowData.SafeOutputs.UpdateIssues.Target != "" { @@ -131,7 +131,7 @@ This workflow tests the update-issue configuration with all options. // Check all options if templatableIntValue(workflowData.SafeOutputs.UpdateIssues.Max) != 3 { - t.Fatalf("Expected max to be 3, got %d", workflowData.SafeOutputs.UpdateIssues.Max) + t.Fatalf("Expected max to be 3, got %v", workflowData.SafeOutputs.UpdateIssues.Max) } if workflowData.SafeOutputs.UpdateIssues.Target != "*" { diff --git a/pkg/workflow/update_project.go b/pkg/workflow/update_project.go index d0541bb64f8..6232f61a1f3 100644 --- a/pkg/workflow/update_project.go +++ b/pkg/workflow/update_project.go @@ -67,7 +67,7 @@ func (c *Compiler) parseUpdateProjectConfig(outputMap map[string]any) *UpdatePro updateProjectConfig.FieldDefinitions = parseProjectFieldDefinitions(configMap, updateProjectLog) } - updateProjectLog.Printf("Parsed update-project config: max=%d, hasCustomToken=%v, hasCustomProject=%v, targetRepo=%q, allowedReposCount=%d, viewCount=%d, fieldDefinitionCount=%d", + updateProjectLog.Printf("Parsed update-project config: max=%v, hasCustomToken=%v, hasCustomProject=%v, targetRepo=%q, allowedReposCount=%d, viewCount=%d, fieldDefinitionCount=%d", updateProjectConfig.Max, updateProjectConfig.GitHubToken != "", updateProjectConfig.Project != "", updateProjectConfig.TargetRepoSlug, len(updateProjectConfig.AllowedRepos), len(updateProjectConfig.Views), len(updateProjectConfig.FieldDefinitions)) return updateProjectConfig } diff --git a/scripts/check-go-toolchain.sh b/scripts/check-go-toolchain.sh index ed2f5e5a31e..e31245dbf26 100644 --- a/scripts/check-go-toolchain.sh +++ b/scripts/check-go-toolchain.sh @@ -10,8 +10,8 @@ if [[ ! "$version" =~ ^go1\.([0-9]+)\.([0-9]+)$ ]]; then fi minor="${BASH_REMATCH[1]}" patch="${BASH_REMATCH[2]}" -if (( minor < 26 || (minor == 26 && patch < 10) || (minor == 27 && patch < 3) )); then - echo "ERROR: ${version} lacks the required CA distrust fix (CVE-2026-111943); use Go 1.26.10, 1.27.3, or a newer supported release" >&2 +if (( minor < 27 || (minor == 27 && patch < 3) )); then + echo "ERROR: ${version} is below the required Go 1.27.3 toolchain baseline; use Go 1.27.3 or a newer supported stable release" >&2 exit 1 fi printf '%s\n' "$version" diff --git a/scripts/check-go-toolchain_test.sh b/scripts/check-go-toolchain_test.sh index 6609a7956a9..56ef418d4be 100644 --- a/scripts/check-go-toolchain_test.sh +++ b/scripts/check-go-toolchain_test.sh @@ -10,10 +10,10 @@ cat >"$TEMP_DIR/go" <<'EOF' printf '%s\n' "$TEST_GO_VERSION" EOF chmod +x "$TEMP_DIR/go" -for version in go1.26.10 go1.26.11 go1.27.3 go1.27.4 go1.28.0; do +for version in go1.27.3 go1.27.4 go1.28.0; do TEST_GO_VERSION="$version" PATH="$TEMP_DIR:$PATH" bash "$SCRIPT_DIR/check-go-toolchain.sh" >/dev/null done -for version in go1.25.20 go1.26.9 go1.27.0 go1.27.1 go1.27.2 go1.28rc1 devel; do +for version in go1.25.20 go1.26.9 go1.26.10 go1.26.11 go1.27.0 go1.27.1 go1.27.2 go1.28rc1 devel; do if TEST_GO_VERSION="$version" PATH="$TEMP_DIR:$PATH" bash "$SCRIPT_DIR/check-go-toolchain.sh" >"$TEMP_DIR/output" 2>&1; then echo "FAIL: accepted unpatched or nonstable toolchain $version" >&2 exit 1 diff --git a/specs/security-architecture-spec.md b/specs/security-architecture-spec.md index 8d362a6a027..03416061479 100644 --- a/specs/security-architecture-spec.md +++ b/specs/security-architecture-spec.md @@ -547,7 +547,7 @@ Destination authorization, endpoint authentication, and artifact authentication |---|---| | **TT-01 TLS policy** | The following hardened bootstrap curl paths MUST restrict both initial and redirected URLs to HTTPS and MUST retain hostname/chain validation: `install_awf_binary.sh`, `install_copilot_cli.sh`, `install_threat_detect_binary.sh`, the GitHub CLI key installer (`install_gh_cli.sh`), Cloud Hypervisor setup downloads (including `cloud_hypervisor_setup_bundle.sh`'s latest-release API probe), and the generated Compose installer in `copilot_engine_installation.go`. No insecure certificate override or HTTP retry is permitted on these paths. The inline Maven bootstrap in `copilot_engine_installation.go` remains outside this TLS policy and TT-02's independent archive-integrity guarantees. | | **TT-02 Independent release integrity** | AWF and downloaded Copilot CLI archives MUST authenticate their checksum manifest against a release-bound SHA256 digest before consuming it. The trusted setup action's `release_integrity.sh` supplies built-in pins; literal workflow `sandbox.agent.checksums-sha256` (AWF `checksums.txt`) and `engine.checksums-sha256` (Copilot `SHA256SUMS.txt`) MUST allow independently provisioned overrides for explicit semantic release versions. Missing, duplicate or malformed trusted pins and mismatched bytes MUST fail installation before archive extraction, installation or execution. Explicit Copilot pins MUST bypass toolcache activation and verify both host and requested musl guest archives. Pins MUST propagate with their versions to matching detection/eval installations; engine normalization MUST NOT carry another engine's version or pin. Replacing both the artifact and its network-served checksum MUST NOT bypass authentication. A bundle integrity failure MUST NOT trigger an AWF binary fallback. Threat-detect retains compiler-supplied asset digests; Compose retains architecture-specific compiler digests and an inline verifier in the generated installer, without sourcing a job-writable helper; `install_gh_cli.sh` authenticates the GitHub CLI apt signing key before installation. Cloud Hypervisor/NVX attestation checks remain independent requirements. | -| **TT-03 Toolchain evidence** | Release builds MUST reject stable Go toolchains older than 1.26.10 on the 1.26 branch or 1.27.3 on the 1.27 branch, unsupported older branches, and development/prerelease toolchains. `scripts/check-go-toolchain.sh` checks the actual compiler; `scripts/build-release.sh` inspects each native binary's embedded toolchain and records `dist/build-toolchain.txt`. The module's preferred toolchain and default generated Go setup are patched; user-selected Go runtimes remain author-controlled and are not attested by compilation. | +| **TT-03 Toolchain evidence** | Release builds MUST reject Go toolchains older than 1.27.3 and development/prerelease toolchains. `scripts/check-go-toolchain.sh` checks the actual compiler; `scripts/build-release.sh` inspects each native binary's embedded toolchain and records `dist/build-toolchain.txt`. Repository modules, lint tooling, the devcontainer and owned workflow Go pins use 1.27.3; user-selected Go runtimes remain author-controlled and are not attested by compilation. Generated root capture and Serena setup MUST clear inherited `GOROOT` when discovering the selected local toolchain and refresh the host environment before subsequent builds. | | **TT-04 Runtime evidence** | Setup MUST log a bounded inventory of available host TLS consumers without credentials, environment dumps, or authenticated URLs. Missing inspection tools and uninspected container/proxy trust stores MUST be identified rather than reported as compliant. `runtime_trust_inventory.sh` supplies host versions; image operators retain responsibility for image contents and actual trust-store selection. | The trusted bootstrap baseline includes the setup action and its digest table, literal author-supplied version/pin pairs, compiler, GitHub Actions control plane, and runner/toolcache provisioning. It excludes attacker-controlled network responses. A preinstalled Copilot CLI from a trusted runner toolcache retains the existing cache policy only when no explicit manifest pin is supplied; cache contents are not newly authenticated by download checks. An arbitrary runner binary, compromised setup action, or malicious workflow author is not repaired by a digest table. From 9bf0bce4e31a306b18e4ee211df6b87f275e741a Mon Sep 17 00:00:00 2001 From: pelikhan Date: Tue, 20 Oct 2026 12:41:39 -0700 Subject: [PATCH 8/8] Refresh WASM golden for Go root capture Update the smoke-copilot fixture to include strict, stale-root-safe GOROOT discovery. Golden regeneration and verification passed; the repository-wide checkpoint retains its previously reported lint blockers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../smoke-copilot.golden | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden index f34c0066d9f..3e57356f421 100644 --- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden +++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden @@ -641,7 +641,16 @@ jobs: go-version: '1.25' cache: false - name: Capture GOROOT for AWF chroot mode - run: echo "GOROOT=$(go env GOROOT)" >> "$GITHUB_ENV" + shell: bash + run: | + set -euo pipefail + unset GOROOT + go_root="$(GOTOOLCHAIN=local go env GOROOT)" + if [ -z "$go_root" ] || [ ! -x "$go_root/bin/go" ]; then + echo "ERROR: Selected Go toolchain has no executable bin/go; check actions/setup-go." >&2 + exit 1 + fi + printf 'GOROOT=%s\n' "$go_root" >> "$GITHUB_ENV" - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise