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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/locked-tools/install-tool.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ $workdir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid()
New-Item -ItemType Directory -Path $workdir | Out-Null
try {
$archive = Join-Path $workdir 'artifact.zip'
Invoke-WebRequest -Uri $url -OutFile $archive -MaximumRetryCount 3
Invoke-WebRequest -Uri $url -OutFile $archive -MaximumRetryCount 5 -RetryIntervalSec 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (medium confidence): Invoke-WebRequest -MaximumRetryCount only retries on HTTP 304/400–599 responses, not on connection resets or mid-body stream failures. So the Windows path does not get the stream-error coverage that --retry-all-errors gives the curl path, which is the incident class this PR targets. If Windows stream errors were part of the 13 failures, wrap the call in a small bounded retry loop (e.g. 5 attempts, Start-Sleep 2).

$actual = (Get-FileHash -Algorithm SHA256 -Path $archive).Hash.ToLowerInvariant()
if ($actual -cne $expected) {
Write-Host "::error title=locked-tools::$Tool sha256 mismatch"
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/locked-tools/install-tool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fi
workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
archive="$workdir/${url##*/}"
curl -sSfL --retry 3 -o "$archive" "$url"
curl -sSfL --retry 5 --retry-delay 2 --retry-all-errors -o "$archive" "$url"
if [ "$os" = darwin ]; then
if ! printf '%s %s\n' "$expected" "$archive" | shasum -a 256 -c -; then
echo "::error title=locked-tools::$tool sha256 mismatch" >&2
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,6 @@ jobs:
runs-on: macos-latest-xlarge
env:
GOTOOLCHAIN: local
GOFLAGS: -mod=readonly
GOPROXY: https://proxy.golang.org
GOSUMDB: sum.golang.org
permissions:
contents: read
id-token: write # <-- needed for cosign keyless (OIDC)
Expand Down
14 changes: 10 additions & 4 deletions docs/release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,12 @@ The preflight `_workflows` checkout and local guard always run. Its
`go_source_hygiene || go_vulnerability_scan` condition applies to locked-tools,
caller checkout, source verification, and the module gate, so disabling both
optional checks does not download a Go toolchain.
Tool-using jobs set `GOTOOLCHAIN=local` at job scope. The binaries job also sets
`GOFLAGS=-mod=readonly`, `GOPROXY=https://proxy.golang.org`, and
`GOSUMDB=sum.golang.org` at job scope. Cosign and bundled-buildx/QEMU setup run
after trust/tool bootstrap and before their signing or AWS credentials.
Tool-using jobs set `GOTOOLCHAIN=local` at job scope. Caller builds do not override
`GOFLAGS`, `GOPROXY`, or `GOSUMDB` at workflow/job scope, preserving Go's default
vendor/readonly mode selection and caller proxy configuration. The trusted gon
build retains its separate step-local module and checksum requirements.
Cosign and bundled-buildx/QEMU setup run after trust/tool bootstrap and before
their signing or AWS credentials.

### Locked Release Tools

Expand All @@ -286,6 +288,10 @@ binaries during extraction. The action's Go assertion and native gon build run
after verified installation. Release credentials are configured only after
tool installation and checks. Helper code checkouts use `job.workflow_sha`.

Artifact downloads use five retries with a two-second delay; curl retries all
errors, including stream failures. PowerShell uses its native retry count and
interval parameters. The committed SHA256 is still checked before extraction.

Go is installed once per tool-using job, with `GOTOOLCHAIN=local`. Each of the
six module gates invokes the checked-out action-local `verify-locked-go.sh`
after source verification and receives the installed action's identity:
Expand Down
8 changes: 7 additions & 1 deletion scripts/test-tool-integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def test_verified_install_never_invokes_binary(self) -> None:
installed = dest / self.archive_member(tool, platform)
self.assertTrue(installed.is_file())
self.assertIn(str(installed.parent), self.path_file.read_text().splitlines())
self.assertIn("--retry 5 --retry-delay 2 --retry-all-errors", self.calls.read_text())
if tool == "go":
self.assertIn(f"GOROOT={dest}/go", self.env_file.read_text())

Expand Down Expand Up @@ -532,7 +533,8 @@ def test_windows_node_installer_fixture(self) -> None:
harness = """
$ErrorActionPreference = 'Stop'
function Invoke-WebRequest {
param([string]$Uri, [string]$OutFile, [int]$MaximumRetryCount)
param([string]$Uri, [string]$OutFile, [int]$MaximumRetryCount, [int]$RetryIntervalSec)
if ($MaximumRetryCount -ne 5 -or $RetryIntervalSec -ne 2) { throw 'retry policy mismatch' }
Add-Content -LiteralPath $env:CALLS -Value "download $Uri"
Copy-Item -LiteralPath $env:ARCHIVE -Destination $OutFile
}
Expand Down Expand Up @@ -641,6 +643,10 @@ def test_release_workflow_has_no_remote_self_actions_or_duplicate_bootstrap(self
self.assertNotIn("gon-source.sh", text)
for legacy in ("_workflows/scripts/install-tool", "_workflows/scripts/gon-source", "_workflows/scripts/verify-locked-go"):
self.assertNotIn(legacy, text)
workflow = json.loads(subprocess.check_output(["yq", ".", str(ROOT / ".github/workflows/release.yaml")], text=True))
scopes = [workflow.get("env", {})] + [job.get("env", {}) for job in workflow["jobs"].values()]
for env in scopes:
self.assertFalse(set(env or {}) & {"GOFLAGS", "GOPROXY", "GOSUMDB"})

def test_workflow_security_wiring(self) -> None:
workflow = json.loads(subprocess.check_output(["yq", ".", str(ROOT / ".github/workflows/release.yaml")], text=True))
Expand Down
Loading