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