Preserve the current upstream Go security toolchain and dependency repair

Compose the exact observed upstream additively without rewriting reviewed tip 805ea6d712 or any accepted ancestry. The new Go 1.26.9 and x/net graph need matching final-source evidence before this batch can be proposed.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-11 23:42:21 +01:00
21 changed files with 46 additions and 40 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.26.8@sha256:0f063af2d465d8dcae54cce04278ada488b96f77b42449c8d071e47d016cc65a
FROM golang:1.26.9@sha256:d7722066f0b60ceccb6c0643cbed1f5f9e15506ac237146c95333504d7805d89
# Set bash as default shell for features
SHELL ["/bin/bash", "-c"]
+3 -3
View File
@@ -5,7 +5,7 @@ This dev container provides a complete, reproducible development environment for
## What's Included
### Development Tools
- **Go 1.26.8** - Backend development
- **Go 1.26.9** - Backend development
- **Node.js 24** - Frontend development
- **gopls v0.17.0** - Go language server
- **Delve** - Go debugger
@@ -225,7 +225,7 @@ Custom overrides: Create `.env.devcontainer` (gitignored)
## Resources
- **VM Specs**: 8GB RAM, 30GB disk, 2 CPU cores
- **Base Image**: `golang:1.26.8` (Debian-based)
- **Base Image**: `golang:1.26.9` (Debian-based)
- **Caches**: ~2-3GB for Go modules and build artifacts
## Tips & Tricks
@@ -247,7 +247,7 @@ MacBook (VS Code)
dev-containers VM (Proxmox)
↓ Docker
Dev Container
├── Go 1.26.8 + tools
├── Go 1.26.9 + tools
├── Node 24 + npm
├── Your code (/workspaces/pulse)
├── Hot reload watchers
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.26.8'
go-version: '1.26.9'
cache: false
- name: Collect fixed exact-pair root evidence
run: python3 scripts/diagnose-root-pair.py --output root-pair-evidence
+1 -1
View File
@@ -26,7 +26,7 @@ RUN --mount=type=cache,id=pulse-npm-cache,target=/root/.npm \
# Build stage for Go backend
# Force amd64 platform - Go cross-compiles for all targets anyway,
# and this avoids slow QEMU emulation during multi-arch builds
FROM --platform=linux/amd64 golang:1.26.8-alpine@sha256:51a7c389a5ddaf82f527191a1e9bff9928655130a44e4975dd1d7e0acf59f1ae AS backend-builder
FROM --platform=linux/amd64 golang:1.26.9-alpine@sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade AS backend-builder
ARG BUILD_AGENT
ARG VERSION
+1 -1
View File
@@ -12,7 +12,7 @@ COPY SECURITY.md TERMS.md /app/
RUN --mount=type=cache,id=pulse-control-plane-npm-cache,target=/root/.npm \
npm run build
FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c AS builder
FROM --platform=$BUILDPLATFORM golang:1.26.9-alpine@sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade AS builder
ARG VERSION=dev
ARG BUILD_TIME=unknown
ARG GIT_COMMIT=unknown
+1 -1
View File
@@ -10,7 +10,7 @@ The source pair is fixed in `scripts/diagnose-root-pair.py`: baseline
`1120cce4d631bca5525cafc5b5620251998773f7` tree measured from ephemeral
source commit `4cdf250f474ad9cde6ffb121527f021b7387603f7`; metadata retains that original
evidence identity. Tree and HTTP source hashes must match. Both API test binaries
use Go 1.26.8, readonly modules and identical CI
use Go 1.26.9, readonly modules and identical CI
embed text; compilation finishes before either warm-up. Four available CPUs
are selected and inherited by children, GOMAXPROCS is four. After two 100ms
warm-ups and ten seconds settling, ten rounds alternate AB/BA with one 1s
@@ -4871,18 +4871,20 @@ governed release toolchain and immutable Go builder digest together, not to
suppress the scanner or produce release artifacts with an older patched-over
runtime.
The hosted control-plane source builder and integration mock builder are both
on the reviewed `golang:1.26.8-alpine` digest
`sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c`.
on the reviewed `golang:1.26.9-alpine` digest
`sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade`.
`TestProviderMSPGoBuilderMatchesIntegrationMock` pins their exact agreement;
it does not pull the image, prove its registry contents, or qualify a deployed
control plane. The release publisher's prebuilt control-plane target is a
separate path and does not rebuild its binary from this stage.
As of 2026-08-27, the governed release floor is Go `1.26.7`. It supersedes
`1.26.5`, whose standard library is reachable through seven vulnerable Pulse
call paths reported by `govulncheck`, including HTTP/TLS, URL parsing, SAML XML
decoding, HTML templating, and public-key parsing. Both source-built container
stages pin the Docker Official Images Linux amd64 manifest
`sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468`;
As of 2026-10-11, the governed release floor is Go `1.26.9`. It supersedes
`1.26.8`, whose standard library is reachable through twelve vulnerable Pulse
call paths reported by `govulncheck` after the 2026-10-08 advisories, including
`net/http`, `crypto/tls`, `html/template`, `os`, and `net/textproto`. The
`golang.org/x/net` floor is `v0.60.0` for the same advisories. Source-built
container stages pin the Docker Official Images manifest list for
`golang:1.26.9-alpine`,
`sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade`;
the checked-in toolchain files and release-script guards must reject an older
compiler so local, exact-candidate, provider control-plane, and container builds
cannot silently reintroduce the vulnerable runtime.
+2 -2
View File
@@ -2,7 +2,7 @@ module github.com/rcourtman/pulse-go-rewrite
go 1.26.0
toolchain go1.26.8
toolchain go1.26.9
require (
github.com/IGLOU-EU/go-wildcard/v2 v2.1.1
@@ -107,7 +107,7 @@ require (
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/net v0.60.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
+2
View File
@@ -288,6 +288,8 @@ golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
+1 -1
View File
@@ -1 +1 @@
go1.26.8
go1.26.9
+1 -1
View File
@@ -44,7 +44,7 @@ case "${OUTPUT_ROOT}" in
;;
esac
required_go="go1.26.8"
required_go="go1.26.9"
current_go="$(go env GOVERSION 2>/dev/null || true)"
if [[ "${current_go}" != "${required_go}" ]]; then
echo "Error: Go toolchain must be ${required_go} (got ${current_go:-unknown})." >&2
+1 -1
View File
@@ -20,7 +20,7 @@ if [ -x /usr/local/go/bin/go ]; then
fi
# Release artifacts must be built with the vetted toolchain to match security-gate evidence.
required_go="go1.26.8"
required_go="go1.26.9"
current_go="$(go env GOVERSION 2>/dev/null || true)"
if [[ "${PULSE_SKIP_GO_VERSION_CHECK:-false}" != "true" ]]; then
if [[ "${current_go}" != "${required_go}" ]]; then
@@ -35,7 +35,7 @@ if [[ -z "${PULSE_UPDATE_SIGNING_PUBLIC_KEY:-}" ]]; then
exit 3
fi
required_go="go1.26.8"
required_go="go1.26.9"
go_version="$(go env GOVERSION 2>/dev/null || true)"
if [[ "${go_version}" != "${required_go}" ]]; then
echo "Error: Go toolchain must be ${required_go} (got ${go_version:-unknown})." >&2
+2 -2
View File
@@ -71,8 +71,8 @@ def telemetry():
def execute(repo, output):
output.mkdir(parents=True, exist_ok=False) # never overwrite a previous attempt
os.environ.update(GOTOOLCHAIN='local', GOMAXPROCS='4', GOFLAGS='-mod=readonly', GOENV='off', GOAMD64='v1', CGO_ENABLED='1')
if run(['go', 'version']).strip() != 'go version go1.26.8 linux/amd64':
raise ValueError('requires exact hosted experiment toolchain go1.26.8 linux/amd64')
if run(['go', 'version']).strip() != 'go version go1.26.9 linux/amd64':
raise ValueError('requires exact hosted experiment toolchain go1.26.9 linux/amd64')
metadata = {
'purpose': 'diagnostic-only; no gate disposition or release candidate',
'rounds': ROUNDS, 'gomaxprocs': 4, 'benchtime': '1s',
+5 -3
View File
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION_FILE="${SCRIPT_DIR}/.go-version"
DEFAULT_VERSION="go1.26.8"
DEFAULT_VERSION="go1.26.9"
TARGET_ROOT="/opt/toolchains/go"
DOWNLOAD_ROOT="https://dl.google.com/go"
GOPATH_DIR="/var/lib/pulse/go"
@@ -14,7 +14,8 @@ if [[ ! -f "$VERSION_FILE" ]]; then
echo "$DEFAULT_VERSION" | sudo tee "$VERSION_FILE" >/dev/null
fi
VERSION="$(tr -d '
' < "$VERSION_FILE")"
ARCHIVE="${VERSION}.linux-amd64.tar.gz"
DOWNLOAD_DIR="${TMPDIR:-/tmp}/go-install"
ARCHIVE_PATH="$DOWNLOAD_DIR/$ARCHIVE"
@@ -28,7 +29,8 @@ fi
curl -fsSL "$DOWNLOAD_ROOT/$ARCHIVE.sha256" -o "$SHA_PATH"
CHECKSUM="$(tr -d '
CHECKSUM="$(tr -d '
' < "$SHA_PATH")"
printf '%s %s
' "$CHECKSUM" "$ARCHIVE_PATH" | sha256sum -c -
@@ -1618,7 +1618,7 @@ func TestDockerBuildUsesCanonicalReleaseLdflags(t *testing.T) {
dockerfile := string(dockerfileBytes)
dockerRequired := []string{
"FROM --platform=linux/amd64 node:24-alpine@sha256:" + node24Amd64FrontendDigest + " AS frontend-builder",
`FROM --platform=linux/amd64 golang:1.26.8-alpine@sha256:`,
`FROM --platform=linux/amd64 golang:1.26.9-alpine@sha256:`,
`FROM backend-builder AS release-assets-builder`,
`AS agent_runtime`,
`AS pulse-runtime-foundation`,
@@ -1653,7 +1653,7 @@ func TestDockerBuildUsesCanonicalReleaseLdflags(t *testing.T) {
}
}
assertDigestPinnedDockerStage(t, dockerfile, `FROM --platform=linux/amd64 node:24-alpine@sha256:`, ` AS frontend-builder`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM --platform=linux/amd64 golang:1.26.8-alpine@sha256:`, ` AS backend-builder`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM --platform=linux/amd64 golang:1.26.9-alpine@sha256:`, ` AS backend-builder`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM alpine:3.24@sha256:`, ` AS agent_runtime`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM alpine:3.24@sha256:`, ` AS pulse-runtime-foundation`)
hostedStart := strings.Index(dockerfile, `FROM pulse-runtime-base AS hosted_runtime`)
@@ -1666,7 +1666,7 @@ func TestDockerBuildUsesCanonicalReleaseLdflags(t *testing.T) {
t.Fatalf("hosted_runtime target must not depend on installer rendering or embedded agent artifacts:\n%s", hostedStage)
}
if strings.Contains(dockerfile, `FROM --platform=linux/amd64 node:24-alpine AS frontend-builder`) ||
strings.Contains(dockerfile, `FROM --platform=linux/amd64 golang:1.26.8-alpine AS backend-builder`) ||
strings.Contains(dockerfile, `FROM --platform=linux/amd64 golang:1.26.9-alpine AS backend-builder`) ||
strings.Contains(dockerfile, `FROM alpine:3.24 AS agent_runtime`) ||
strings.Contains(dockerfile, `FROM alpine:3.24 AS pulse-runtime-base`) {
t.Fatal("Dockerfile base images must be pinned by immutable @sha256 digests")
@@ -13,7 +13,7 @@ func TestIntegrationContainersUseGovernedImmutableBases(t *testing.T) {
t.Fatalf("read mock GitHub Dockerfile: %v", err)
}
dockerfile := string(dockerfileBytes)
assertDigestPinnedDockerStage(t, dockerfile, `FROM golang:1.26.8-alpine@sha256:`, ` AS builder`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM golang:1.26.9-alpine@sha256:`, ` AS builder`)
assertDigestPinnedDockerStage(t, dockerfile, `FROM alpine:3.24@sha256:`, ``)
composeBytes, err := os.ReadFile(repoFile("tests", "integration", "docker-compose.test.yml"))
@@ -15,7 +15,7 @@ func TestProviderMSPGoBuilderMatchesIntegrationMock(t *testing.T) {
// These builders use the same Go patch level and immutable image. The
// mock's build must not exercise a different compiler from the hosted
// control plane merely because Dependabot advanced one Dockerfile.
const expected = "golang:1.26.8-alpine@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c"
const expected = "golang:1.26.9-alpine@sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade"
pattern := regexp.MustCompile(`(?m)^FROM (?:--platform=\$BUILDPLATFORM )?(golang:[^[:space:]]+) AS builder$`)
for _, path := range [][]string{
{"deploy", "provider-msp", "Dockerfile.control-plane"},
@@ -328,7 +328,7 @@ func TestProviderMSPControlPlaneDockerfileBuildsReleaseLicenseBinary(t *testing.
"FROM --platform=linux/amd64 node:24-alpine@sha256:"+node24Amd64FrontendDigest+" AS frontend-builder",
"npm ci",
"npm run build",
"FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine@sha256:",
"FROM --platform=$BUILDPLATFORM golang:1.26.9-alpine@sha256:",
"FROM alpine:3.24@sha256:",
"ARG PULSE_LICENSE_PUBLIC_KEY_SHA256",
"ARG TARGETOS",
@@ -355,7 +355,7 @@ func TestProviderMSPControlPlaneDockerfileBuildsReleaseLicenseBinary(t *testing.
"CGO_ENABLED=0 go build -o /pulse-control-plane ./cmd/pulse-control-plane",
)
assertDigestPinnedDockerStage(t, text, `FROM --platform=linux/amd64 node:24-alpine@sha256:`, ` AS frontend-builder`)
assertDigestPinnedDockerStage(t, text, `FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine@sha256:`, ` AS builder`)
assertDigestPinnedDockerStage(t, text, `FROM --platform=$BUILDPLATFORM golang:1.26.9-alpine@sha256:`, ` AS builder`)
assertDigestPinnedDockerStage(t, text, `FROM alpine:3.24@sha256:`, ` AS control-plane-runtime-foundation`)
}
+3 -3
View File
@@ -414,9 +414,9 @@ test_go_release_toolchain_floor() {
module_toolchain="$(sed -n 's/^toolchain //p' "${ROOT_DIR}/go.mod")"
installed_toolchain="$(cd "${ROOT_DIR}" && go env GOVERSION)"
assert_contains "Go module uses the patched release toolchain" "${module_toolchain}" "go1.26.8"
assert_contains "dev toolchain installer uses the patched release toolchain" "$(<"${ROOT_DIR}/scripts/.go-version")" "go1.26.8"
assert_contains "active dev runtime resolves the patched release toolchain" "${installed_toolchain}" "go1.26.8"
assert_contains "Go module uses the patched release toolchain" "${module_toolchain}" "go1.26.9"
assert_contains "dev toolchain installer uses the patched release toolchain" "$(<"${ROOT_DIR}/scripts/.go-version")" "go1.26.9"
assert_contains "active dev runtime resolves the patched release toolchain" "${installed_toolchain}" "go1.26.9"
}
test_backend_race_suite_keeps_hosted_runner_timeout_headroom() {
+2 -2
View File
@@ -56,7 +56,7 @@ class DiagnosticTests(unittest.TestCase):
def fake_run(args, cwd=None):
if args == ['go', 'version']:
return 'go version go1.26.7 linux/amd64' if bad_go else 'go version go1.26.8 linux/amd64'
return 'go version go1.26.7 linux/amd64' if bad_go else 'go version go1.26.9 linux/amd64'
if args[:2] == ['git', 'rev-parse']:
return 'wrong' if bad_tree else diag.TREES[args[2].split('^')[0]]
if args[:3] == ['go', 'tool', 'nm']:
@@ -151,7 +151,7 @@ class DiagnosticTests(unittest.TestCase):
self.assertIn('[[ "$GITHUB_RUN_ATTEMPT" == 1 ]]', workflow)
self.assertIn('path: root-pair-evidence/', workflow)
self.assertIn('if: always()', workflow)
self.assertIn("go-version: '1.26.8'", workflow)
self.assertIn("go-version: '1.26.9'", workflow)
self.assertIn('timeout-minutes: 45', workflow)
@@ -1,4 +1,4 @@
FROM golang:1.26.8-alpine@sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c AS builder
FROM golang:1.26.9-alpine@sha256:3082400e369fa24d5fc60bca20edab3f6d604e0c5a690ec66b295eff4dd87ade AS builder
WORKDIR /build
COPY go.mod ./