From 924e0783d1dd8b724fa402cd89f92b29a4c2c52b Mon Sep 17 00:00:00 2001 From: Jonathan Moss <2729151+jwmoss@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:05:37 -0400 Subject: [PATCH] chore: harden the release pipeline and installers Drop the unused winget token from the release job, stop running tests with release credentials, use commit timestamps for reproducible builds, add workflow concurrency groups and a gomod cooldown, require HTTPS and TLS 1.2 in the installers, preserve REG_EXPAND_SZ PATH entries on Windows, and document the security reporting channel. --- .github/dependabot.yml | 2 ++ .github/workflows/ci.yml | 4 ++++ .github/workflows/release.yml | 7 +++++-- .goreleaser.yaml | 8 ++++---- CHANGELOG.md | 8 ++++++++ SECURITY.md | 17 +++++++++++++++-- install/install.ps1 | 26 +++++++++++++++++--------- install/install.sh | 6 +++--- 8 files changed, 58 insertions(+), 20 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index df612d3..f240a45 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,8 @@ updates: directory: / schedule: interval: weekly + cooldown: + default-days: 7 groups: go: patterns: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54dbc1f..e819986 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 866c827..43990c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,11 +8,15 @@ on: permissions: contents: read +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: goreleaser: runs-on: ubuntu-latest permissions: - contents: write + contents: write # goreleaser uploads the release archives and checksums steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -31,7 +35,6 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }} windows-download: needs: goreleaser diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8f56bab..25af77e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,14 +2,14 @@ version: 2 project_name: datatf -before: - hooks: - - go test ./... +# CI runs the test suite on a token-free runner before a tag is cut. Tests do not run +# inside the credentialed release job. builds: - id: datatf main: ./cmd/datatf binary: datatf + mod_timestamp: "{{ .CommitTimestamp }}" env: - CGO_ENABLED=0 flags: @@ -25,7 +25,7 @@ builds: - -s -w - -X github.com/536tech/datatf/internal/cli.version={{ .Version }} - -X github.com/536tech/datatf/internal/cli.commit={{ .ShortCommit }} - - -X github.com/536tech/datatf/internal/cli.date={{ .Date }} + - -X github.com/536tech/datatf/internal/cli.date={{ .CommitDate }} archives: - id: datatf ids: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0061bd7..06504f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.0.3] - 2026-09-11 +### Security + +- The release job no longer receives the winget token or runs tests with release credentials. +- Release builds use the commit timestamp for the build date and module timestamps. +- The installers require HTTPS and TLS 1.2, and match checksum entries exactly. +- The Windows installer keeps unexpanded `%VAR%` entries in the user PATH. +- `SECURITY.md` lists the reporting channel, response window, and supported versions. + ### Changed - Telemetry is on by default for interactive sessions. CI and agent sessions still send nothing diff --git a/SECURITY.md b/SECURITY.md index a95c9f0..5d74ea4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,21 @@ ## Reporting -Report security issues privately through GitHub security advisories or by -contacting the maintainer directly. Do not include credentials in the report. +Report security issues privately through +[GitHub security advisories](https://github.com/536tech/datatf/security/advisories/new). +Do not include credentials in the report. Do not open a public issue for a security problem. + +The maintainer acknowledges reports within 7 days and aims to publish a fix within 90 days. + +## Supported versions + +Only the latest release on the 1.x line receives security fixes. + +## Distribution + +Release archives ship with a `checksums.txt` file. The installers verify the archive +checksum. Archives are not yet signed. Verify the checksum from the release page before you +use a manually downloaded archive. ## Runtime diff --git a/install/install.ps1 b/install/install.ps1 index 6333814..1ad1d43 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -3,6 +3,7 @@ # Installs the latest release into %LOCALAPPDATA%\datatf\bin and adds it to the # user PATH. Set $env:DATATF_VERSION, such as 1.0.0, to pin a release. $ErrorActionPreference = "Stop" +[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $repo = "536tech/datatf" $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq "Arm64") { "arm64" } else { "amd64" } @@ -19,12 +20,12 @@ $tmp = Join-Path ([System.IO.Path]::GetTempPath()) "datatf-install-$([guid]::New New-Item -ItemType Directory -Force -Path $dest, $tmp | Out-Null try { Write-Host "Downloading $asset..." - Invoke-WebRequest "$base/$asset" -OutFile (Join-Path $tmp $asset) - Invoke-WebRequest "$base/checksums.txt" -OutFile (Join-Path $tmp "checksums.txt") + Invoke-WebRequest "$base/$asset" -OutFile (Join-Path $tmp $asset) -UseBasicParsing + Invoke-WebRequest "$base/checksums.txt" -OutFile (Join-Path $tmp "checksums.txt") -UseBasicParsing - $line = Select-String -Path (Join-Path $tmp "checksums.txt") -SimpleMatch " $asset" | Select-Object -First 1 + $line = Get-Content (Join-Path $tmp "checksums.txt") | Where-Object { ($_ -split ' +')[1] -eq $asset } | Select-Object -First 1 if (-not $line) { throw "checksums.txt has no entry for $asset" } - $expected = $line.Line.Split(" ")[0].ToLower() + $expected = ($line -split ' +')[0].ToLower() $actual = (Get-FileHash (Join-Path $tmp $asset) -Algorithm SHA256).Hash.ToLower() if ($expected -ne $actual) { throw "checksum mismatch for $asset" } @@ -34,10 +35,17 @@ try { Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue } -$userPath = [Environment]::GetEnvironmentVariable("Path", "User") -if (($userPath -split ";") -notcontains $dest) { - [Environment]::SetEnvironmentVariable("Path", "$userPath;$dest", "User") - $env:Path = "$env:Path;$dest" - Write-Host "Added $dest to your user PATH. Open a new terminal to use it." +# Read and write the raw registry value so %VAR% entries in the user PATH stay unexpanded. +$envKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Environment", $true) +try { + $userPath = [string]$envKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + if (($userPath -split ";") -notcontains $dest) { + $newPath = if ($userPath) { "$userPath;$dest" } else { $dest } + $envKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString) + $env:Path = "$env:Path;$dest" + Write-Host "Added $dest to your user PATH. Open a new terminal to use it." + } +} finally { + $envKey.Close() } & (Join-Path $dest "datatf.exe") version diff --git a/install/install.sh b/install/install.sh index 54fd187..0cb60c3 100755 --- a/install/install.sh +++ b/install/install.sh @@ -35,9 +35,9 @@ tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT echo "Downloading $asset..." -curl -fsSL -o "$tmp/$asset" "$base/$asset" -curl -fsSL -o "$tmp/checksums.txt" "$base/checksums.txt" -expected="$(grep " $asset\$" "$tmp/checksums.txt" | cut -d' ' -f1)" +curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSL -o "$tmp/$asset" "$base/$asset" +curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fsSL -o "$tmp/checksums.txt" "$base/checksums.txt" +expected="$(awk -v asset="$asset" '$2 == asset { print $1 }' "$tmp/checksums.txt")" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "$tmp/$asset" | cut -d' ' -f1)" else