From 0ccbb7c919d2a98118fbc111af95467d92bb84f2 Mon Sep 17 00:00:00 2001
From: chen21019 <19357113+chen21019@users.noreply.github.com>
Date: Sun, 25 Oct 2026 08:30:16 +0800
Subject: [PATCH 1/2] Release Server v1.6.552 with qualified runtime security
backports
---
.github/workflows/publish-current-server.yml | 56 ++++++---
COMPATIBILITY.md | 28 +++++
README.md | 28 ++---
docs/releases/server-1.6.552.md | 34 ++++++
scripts/check-server-api-explorer-patch.sh | 24 ++--
.../check-server-runtime-image-references.sh | 2 +-
.../test-current-server-packaging-contract.py | 46 ++++----
.../test-server-backport-consumer-contract.py | 106 ++++++++++++++++++
server/Dockerfile.web-compose-release | 77 ++++++++-----
server/build-api-explorer-patch-image.sh | 21 ++--
server/build-component-candidate.sh | 2 +-
server/security/openvex.json | 4 +-
server/security/vendor-pending.json | 25 +----
13 files changed, 328 insertions(+), 125 deletions(-)
create mode 100644 docs/releases/server-1.6.552.md
create mode 100644 scripts/test-server-backport-consumer-contract.py
diff --git a/.github/workflows/publish-current-server.yml b/.github/workflows/publish-current-server.yml
index 5ff3c9f902..ecfe1cf715 100644
--- a/.github/workflows/publish-current-server.yml
+++ b/.github/workflows/publish-current-server.yml
@@ -72,6 +72,7 @@ jobs:
python3 source/scripts/test-render-server-release-notes.py
python3 source/scripts/test-publisher-safety-guards.py
python3 source/scripts/test-web-console-sbom.py
+ python3 source/scripts/test-server-backport-consumer-contract.py
declared_release="$(sed -n 's/^ARG SERVER_RELEASE_TAG=//p' \
source/server/Dockerfile.web-compose-release | sort -u)"
@@ -556,6 +557,45 @@ jobs:
' "$evidence/server-nested-payloads.json" >>"$evidence/server-secrets.tsv"
test ! -s "$evidence/server-secrets.tsv"
+ docker run --rm \
+ -v "$RUNNER_TEMP/server-rootfs:/rootfs:ro" \
+ -v "$evidence:/evidence" \
+ -v "$RUNNER_TEMP/trivy-cache:/root/.cache/trivy" \
+ "$TRIVY_IMAGE" rootfs \
+ --format cyclonedx \
+ --output /evidence/server.cdx.json \
+ /rootfs
+ jq -e '
+ .bomFormat == "CycloneDX"
+ and ((.components | type) == "array")
+ and ((.components | length) > 0)
+ ' "$evidence/server.cdx.json" >/dev/null
+
+ # Bind the candidate's retained metadata to the same immutable OCI
+ # before passing its exact-package VEX to native Trivy.
+ cp -a "$RUNNER_TEMP/server-rootfs/usr/share/pasturestack/security/ubuntu-backports" "$evidence/ubuntu-backports"
+ (cd "$evidence/ubuntu-backports" && sha256sum --check SHA256SUMS)
+ bundle_image="$(sed -n 's/^FROM \(ghcr.io\/pasturestack\/ubuntu-security-backports:v0\.1\.0@sha256:[0-9a-f]\{64\}\) AS ubuntu_security_backports$/\1/p' source/server/Dockerfile.web-compose-release)"
+ [[ "$bundle_image" =~ ^ghcr.io/pasturestack/ubuntu-security-backports:v0\.1\.0@sha256:[0-9a-f]{64}$ ]]
+ docker pull --platform linux/amd64 "$bundle_image"
+ mkdir -p "$evidence/bundle-tools"
+ bundle_container="$(docker create --entrypoint /unused "$bundle_image")"
+ trap 'docker rm "$bundle_container" >/dev/null' EXIT
+ docker cp "$bundle_container:/out/verify-consumer-evidence.py" "$evidence/bundle-tools/verify-consumer-evidence.py"
+ docker cp "$bundle_container:/out/SHA256SUMS" "$evidence/bundle-tools/SHA256SUMS"
+ docker rm "$bundle_container" >/dev/null
+ trap - EXIT
+ (cd "$evidence/bundle-tools" && \
+ test "$(grep -Ec '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS)" -eq 1 && \
+ grep -E '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS | sha256sum --check -)
+ python3 "$evidence/bundle-tools/verify-consumer-evidence.py" \
+ --evidence-dir "$evidence/ubuntu-backports" --sbom "$evidence/server.cdx.json" \
+ --expected-bundle-digest "${bundle_image##*@}" \
+ --bundle-checksums "$evidence/bundle-tools/SHA256SUMS" \
+ >"$evidence/ubuntu-backports-validation.json"
+ tar -cf "$evidence/ubuntu-backports-evidence.tar" -C "$evidence" \
+ ubuntu-backports bundle-tools ubuntu-backports-validation.json
+
docker run --rm \
-v "$RUNNER_TEMP/server-rootfs:/rootfs:ro" \
-v "$evidence:/evidence" \
@@ -564,6 +604,7 @@ jobs:
--scanners vuln \
--skip-vex-repo-update \
--vex /evidence/server.openvex.json \
+ --vex /evidence/ubuntu-backports/openvex.json \
--format json \
--output /evidence/server-security-scan.json \
/rootfs
@@ -588,19 +629,6 @@ jobs:
"$evidence/server-vulnerabilities-unresolved.tsv" \
"$RELEASE_TAG" | tee "$evidence/vendor-pending-summary.txt"
- docker run --rm \
- -v "$RUNNER_TEMP/server-rootfs:/rootfs:ro" \
- -v "$evidence:/evidence" \
- -v "$RUNNER_TEMP/trivy-cache:/root/.cache/trivy" \
- "$TRIVY_IMAGE" rootfs \
- --format cyclonedx \
- --output /evidence/server.cdx.json \
- /rootfs
- jq -e '
- .bomFormat == "CycloneDX"
- and ((.components | type) == "array")
- and ((.components | length) > 0)
- ' "$evidence/server.cdx.json" >/dev/null
raw_count="$(wc -l <"$evidence/server-vulnerabilities-raw.tsv")"
vendor_pending_count="$(wc -l <"$evidence/server-vulnerabilities-unresolved.tsv" | tr -d ' ')"
browser_scan_coverage="$(jq -r '.coverage' "$evidence/web-console-runtime-scan-coverage.json")"
@@ -760,6 +788,7 @@ jobs:
security-summary.txt \
server.openvex.json \
server.vendor-pending.json \
+ ubuntu-backports-evidence.tar \
server-security-scan-raw.json \
server-security-scan.json \
server-nested-payloads.json \
@@ -800,6 +829,7 @@ jobs:
"$evidence/security-summary.txt" \
"$evidence/server.openvex.json" \
"$evidence/server.vendor-pending.json" \
+ "$evidence/ubuntu-backports-evidence.tar" \
"$evidence/server-security-scan-raw.json" \
"$evidence/server-security-scan.json" \
"$evidence/server-nested-payloads.json" \
diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md
index bbc4104441..149bafecf9 100644
--- a/COMPATIBILITY.md
+++ b/COMPATIBILITY.md
@@ -1,5 +1,33 @@
# Compatibility Contract
+
+
+## Server v1.6.552 runtime backports
+
+Use the numeric `ghcr.io/pasturestack/server:v1.6.552` image tag. Its exact
+producer, immutable OCI digest, SBOM, original vulnerability report and interpreted
+report are published in the [release assets](https://github.com/PastureStack/server/releases/tag/v1.6.552).
+
+The digest-pinned, build-only Ubuntu backport bundle installs only existing
+packages. It retains the original package set, selected coreutils provider and
+configuration. The Python compiler keeps its original inputs; the final runtime
+uses the qualified library packages. Source/native-test qualification and actual
+installation receipts are retained under
+`/usr/share/pasturestack/security/ubuntu-backports/`. These downstream Debian
+packages apply accepted upstream fixes to pinned Ubuntu sources; they are not
+packages issued by Canonical.
+
+Node Agent `v1.2.33` pins base `v0.3.7`. Catalog `v0.3.17` records the component
+producer commits, image digests, SBOMs and scan evidence. Existing environments
+retain their configuration and require the normal infrastructure-stack upgrade
+flow to adopt new Catalog revisions. No host firewall backend is selected or
+changed by the runtime library installer.
+
+Available fixes and unreviewed findings block publication. Officially unfixed
+Low/Medium findings remain registered; exact-package code-absence decisions
+require separate source, package and native-scanner evidence. The original raw
+report is retained. See the [release note](docs/releases/server-1.6.552.md).
+
## Server v1.6.551 published
diff --git a/README.md b/README.md
index 370a375587..6ccdf34477 100644
--- a/README.md
+++ b/README.md
@@ -13,21 +13,21 @@ upstream history, authorship, licenses, and notices.
## Current release
-Server [`v1.6.551`](https://github.com/PastureStack/server/releases/tag/v1.6.551) is published.
-It retains the eight PCRE2 CVE fixes using the upstream-supported 10.46 backports and
-original Ubuntu packaging. The two rebuilt runtime packages retain ABI, JIT,
-POSIX interfaces and hardening. Its standalone Host API archive matches the
-original producer; the incorrect 550 attachment is superseded.
+Server [`v1.6.552`](https://github.com/PastureStack/server/releases/tag/v1.6.552)
+includes Ubuntu security backports from verified upstream releases and accepted
+fixes. Each image updates only packages already installed, preserving its package
+set, coreutils provider, configuration and library interfaces. The compiler stages
+remain separate from the final runtime.
Web Console `1.6.195` and its original runtime SBOM, Engine `v0.183.354`,
Host API `0.38.8` and Temurin `25.0.5+8` retain their verified inputs.
-The scoped 8080 upgrade and restart passed package/hash/linkage checks,
-regex controls in grep, Git and MariaDB, and three active Host/Agent states.
-Existing configuration, volumes, AppArmor and the previous rollback image remain.
+Node Agent `v1.2.33` uses compatibility base `v0.3.7`; Catalog `v0.3.17`
+references the corresponding verified component images.
-See the [551 release note](docs/releases/server-1.6.551.md) and
-[compatibility contract](COMPATIBILITY.md#server-v16551-release-assets)
-for the immutable identity, verified assets and remaining risks.
+See the [release note](docs/releases/server-1.6.552.md),
+[release assets](https://github.com/PastureStack/server/releases/tag/v1.6.552) and
+[compatibility contract](COMPATIBILITY.md#server-v16552-runtime-backports)
+for producer identities, immutable image digests, SBOMs and remaining findings.
Personal API Keys remain available without an accessible environment. See the [Host guide](docs/hosts/README.md) before enrolling a host.
@@ -54,14 +54,14 @@ Maintained CLI `0.6.20` propagates rejected requests as nonzero errors. Legacy `
## Quick start
Public GHCR downloads do not require a registry login. Keep all three named volumes.
-The examples below use the published numeric `v1.6.551` tag.
+The examples below use the numeric `v1.6.552` tag.
```sh
docker run -d --name pasturestack-server --restart unless-stopped -p 8080:8080 \
-v pasturestack-cattle:/var/lib/cattle \
-v pasturestack-mysql:/var/lib/mysql \
-v pasturestack-mysqllog:/var/log/mysql \
- ghcr.io/pasturestack/server:v1.6.551
+ ghcr.io/pasturestack/server:v1.6.552
```
For HTTPS termination at a reverse proxy, set the exact public origin:
@@ -69,7 +69,7 @@ For HTTPS termination at a reverse proxy, set the exact public origin:
```yaml
services:
pasturestack-server:
- image: ghcr.io/pasturestack/server:v1.6.551
+ image: ghcr.io/pasturestack/server:v1.6.552
restart: unless-stopped
ports:
- "8080:8080"
diff --git a/docs/releases/server-1.6.552.md b/docs/releases/server-1.6.552.md
new file mode 100644
index 0000000000..82edbfe388
--- /dev/null
+++ b/docs/releases/server-1.6.552.md
@@ -0,0 +1,34 @@
+# Server v1.6.552
+
+This release consumes the qualified Ubuntu security backports in Server and its
+component images. It updates only already-installed packages, preserves the
+coreutils provider and configuration, and keeps compilers outside the final
+runtime. The root Server image does not add jq, Rust coreutils or development
+packages to obtain a clean scan.
+
+The bundle binds the original upstream fixes, native package tests, actual GNU
+and uutils installation fixtures, package archives and exact-package OpenVEX.
+The published image has its own SBOM and installation verification. These are
+separate from the bundle's data SBOM. Raw findings remain available alongside
+the interpreted report. Unfixed findings and code-absence determinations are
+reported separately from fixed vulnerabilities.
+
+Web Console `1.6.195`, Engine `v0.183.354`, Host API `0.38.8` and Temurin
+`25.0.5+8` retain their component inputs. Node Agent `v1.2.33` uses base
+`v0.3.7`; Catalog `v0.3.17` records the corresponding component releases.
+
+## Upgrade and rollback
+
+Back up the database and all three named volumes before upgrading. Keep the
+existing Compose environment variables, ports, restart policy, AppArmor,
+public HTTPS origin and mounts. Use `ghcr.io/pasturestack/server:v1.6.552`;
+do not keep old runtime bundle overrides mounted into the new image.
+
+Upgrade existing infrastructure stacks through the normal Catalog workflow and
+complete their upgrades after checking health. Preserve the previous image and
+matching data backup for rollback. Do not run `docker compose down -v`.
+
+The official release includes the producer commit, immutable image digest,
+checksums, SBOMs, vulnerability reports and qualification receipts. Consult
+[the compatibility contract](../../COMPATIBILITY.md#server-v16552-runtime-backports)
+for the retained runtime boundaries.
diff --git a/scripts/check-server-api-explorer-patch.sh b/scripts/check-server-api-explorer-patch.sh
index 7955d5cce1..44a5d66dc7 100755
--- a/scripts/check-server-api-explorer-patch.sh
+++ b/scripts/check-server-api-explorer-patch.sh
@@ -570,7 +570,7 @@ python3 scripts/test-web-console-sbom.py
python3 server/security/test_web_console_sbom_scan.py
require_marker "$release_dockerfile" \
- 'ARG SERVER_RELEASE_TAG=v1.6.551' \
+ 'ARG SERVER_RELEASE_TAG=v1.6.552' \
SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING
require_marker "$release_dockerfile" \
'org.opencontainers.image.version="${SERVER_RELEASE_TAG}"' \
@@ -1138,10 +1138,10 @@ require_marker "$build_script" \
'--file server/Dockerfile.web-compose-release' \
SERVER_INCREMENTAL_RELEASE_BUILD_PATH_MISSING
require_marker "$build_script" \
- 'image=${IMAGE:-pasturestack-validation/server:v1.6.551}' \
+ 'image=${IMAGE:-pasturestack-validation/server:v1.6.552}' \
SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING
require_marker "$build_script" \
- 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.551}' \
+ 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.552}' \
SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING
require_marker "$build_script" \
'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"' \
@@ -1597,13 +1597,13 @@ require_marker "$build_script" \
'grep -F "pasturestack-catalog-pinned-commit"' \
SERVER_CATALOG_PINNED_COMMIT_IMAGE_GATE_MISSING
require_marker "$release_dockerfile" \
- 'ENV PASTURESTACK_CATALOG_COMMIT=070e08d8f85ffc2779a0f97eb4e356c857812d34' \
+ 'ENV PASTURESTACK_CATALOG_COMMIT=72b0d9767306baf7091fca021d0a2b6bdbca5d51' \
SERVER_CATALOG_VERSION_LABEL_COMMIT_MISSING
require_marker "$release_dockerfile" \
- '"pinnedCommit":"070e08d8f85ffc2779a0f97eb4e356c857812d34"' \
+ '"pinnedCommit":"72b0d9767306baf7091fca021d0a2b6bdbca5d51"' \
SERVER_CATALOG_VERSION_LABEL_URL_MISSING
require_marker "$build_script" \
- 'PASTURESTACK_CATALOG_COMMIT=070e08d8f85ffc2779a0f97eb4e356c857812d34' \
+ 'PASTURESTACK_CATALOG_COMMIT=72b0d9767306baf7091fca021d0a2b6bdbca5d51' \
SERVER_CATALOG_VERSION_LABEL_IMAGE_GATE_MISSING
for previous_release_marker in \
'# Server v1.6.462' \
@@ -1691,10 +1691,10 @@ fi
if grep -Fxq '## Current source candidate' README.md; then
for candidate_readme_marker in \
'' \
- 'Server `v1.6.551` is a prepared source candidate, not a published image.' \
+ 'Server `v1.6.552` is a prepared source candidate, not a published image.' \
'run them only after its official release' \
'This candidate is not yet available' \
- 'ghcr.io/pasturestack/server:v1.6.551'; do
+ 'ghcr.io/pasturestack/server:v1.6.552'; do
require_marker README.md "$candidate_readme_marker" \
SERVER_CURRENT_CANDIDATE_README_MISSING
done
@@ -1980,7 +1980,7 @@ require_marker "$build_script" \
'PASTURESTACK_CURL_PACKAGE_VERSION=8.18.0-1ubuntu2.8' \
SERVER_CURL_IMAGE_VERSION_GATE_MISSING
require_marker "$build_script" \
- 'PASTURESTACK_GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4' \
+ 'PASTURESTACK_GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4+pasturestack.1' \
SERVER_GLIBC_IMAGE_VERSION_GATE_MISSING
require_marker "$build_script" \
'PASTURESTACK_PERL_PACKAGE_VERSION=5.40.1-7ubuntu0.3' \
@@ -2300,7 +2300,7 @@ done
jq -e '
["CVE-2026-107928","CVE-2026-107929","CVE-2026-86409","CVE-2026-86410","CVE-2026-86412","CVE-2026-86414","CVE-2026-87862","CVE-2026-87863","CVE-2026-87864","CVE-2026-87865","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"] as $curl_fixed_cves
| .["@context"] == "https://openvex.dev/ns/v0.2.0"
- and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.551"
+ and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.552"
and (.statements | length) == 56
and ([.statements[].vulnerability.name] | length == (unique | length))
and ([.statements[] | select(.status == "fixed") | .vulnerability.name] | sort)
@@ -2338,7 +2338,7 @@ jq -r '
| @tsv
' "$runtime_vendor_pending" | LC_ALL=C sort -u >"$vendor_pending_fixture"
bash "$vendor_pending_validator" "$runtime_vendor_pending" \
- "$vendor_pending_fixture" v1.6.551 >/dev/null
+ "$vendor_pending_fixture" v1.6.552 >/dev/null
bash scripts/test-vendor-pending-findings.sh >/dev/null
rm -f "$vendor_pending_fixture"
trap - EXIT
@@ -2427,4 +2427,4 @@ for release_readback_contract in \
fi
done
-printf 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.551 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 webhook_automation_service=0.10.5 authentication_service=0.4.46 curl=8.18.0-1ubuntu2.8 freemarker=2.3.35 artifact_scan=required vendor_pending=exact-set role_matrix=qa-required locale_layout=qa-required\n'
+printf 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.552 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 webhook_automation_service=0.10.5 authentication_service=0.4.46 curl=8.18.0-1ubuntu2.8 freemarker=2.3.35 artifact_scan=required vendor_pending=exact-set role_matrix=qa-required locale_layout=qa-required\n'
diff --git a/scripts/check-server-runtime-image-references.sh b/scripts/check-server-runtime-image-references.sh
index ed50a1b416..4d1d0c38c0 100755
--- a/scripts/check-server-runtime-image-references.sh
+++ b/scripts/check-server-runtime-image-references.sh
@@ -6,7 +6,7 @@ cd "$repo_root"
approved_registry=ghcr.io/pasturestack
approved_agent="${approved_registry}/node-agent:v1.2.31"
-current_agent="${approved_registry}/node-agent:v1.2.32"
+current_agent="${approved_registry}/node-agent:v1.2.33"
approved_balancer="${approved_registry}/load-balancer-service:v0.9.33"
# Historical recipes and opt-in migration tools keep their reviewed coordinates.
historical_balancer="${approved_registry}/load-balancer-service:v0.9.27"
diff --git a/scripts/test-current-server-packaging-contract.py b/scripts/test-current-server-packaging-contract.py
index 1412712d24..1fbb9884e3 100644
--- a/scripts/test-current-server-packaging-contract.py
+++ b/scripts/test-current-server-packaging-contract.py
@@ -38,11 +38,11 @@
WEB_SOURCE = 'f905bc12daae18d98a6ac65942625562fff2a424'
WEB_SBOM_SHA = 'ca31669f58aa342cd346e1d33ec7061d65f9b84d6a3afcbd132e09361425a88a'
WEB_LOCK_SHA = 'ae862be4519751895f74e552f036906d64be8371fdbbc6379c16e52518f9ee22'
-CATALOG_COMMIT = '070e08d8f85ffc2779a0f97eb4e356c857812d34'
+CATALOG_COMMIT = '72b0d9767306baf7091fca021d0a2b6bdbca5d51'
ENGINE_SHA = 'f4889bfb8759a7664f07524bb129d0cda19f35604a06c877b6ca7033246df6fa'
ENGINE_SOURCE = 'dc5a8d7bc44131f609f72a6504483b692d47ce16'
OLD_COORDINATES = {
- 'v1.6.551': 'v1.6.521',
+ 'v1.6.552': 'v1.6.521',
'1.6.195': '1.6.181',
WEB_SHA: '9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03',
WEB_SOURCE: '7f2bdad0c90a3241693c2ddd4475202ababba550',
@@ -66,7 +66,7 @@
}
EXPECTED = {
COMPATIBILITY_CODE: (COMPATIBILITY, PUBLISHED_508),
- 'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.551'),
+ 'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.552'),
'SERVER_INCREMENTAL_RELEASE_VERSION_MISSING': (DOCKER, 'org.opencontainers.image.version="${SERVER_RELEASE_TAG}"'),
'SERVER_INCREMENTAL_RELEASE_RUNTIME_VERSION_MISSING': (DOCKER, 'ENV CATTLE_RANCHER_SERVER_VERSION=${SERVER_RELEASE_TAG}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_VERSION_MISSING': (DOCKER, 'ARG WEB_CONSOLE_RELEASE_TAG=1.6.195'),
@@ -81,8 +81,8 @@
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_HASH_MISSING': (BUILD, 'web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-' + WEB_SHA + '}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_LOCK_HASH_MISSING': (BUILD, 'web_console_lock_sha256=${WEB_CONSOLE_LOCK_SHA256:-' + WEB_LOCK_SHA + '}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_SBOM_HASH_MISSING': (BUILD, 'web_console_runtime_sbom_sha256=${WEB_CONSOLE_RUNTIME_SBOM_SHA256:-' + WEB_SBOM_SHA + '}'),
- 'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.551}'),
- 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.551}'),
+ 'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.552}'),
+ 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.552}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_RUNTIME_VERSION_MISSING': (BUILD, 'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"'),
'SERVER_WEB_CONSOLE_RUNTIME_VERSION_GATE_MISSING': (BUILD, 'test "$(cat "${web_root}/VERSION.txt")" = "${WEB_CONSOLE_RELEASE_TAG}"'),
'SERVER_VOLUME_NATIVE_READ_ONLY_BUILD_GATE_MISSING': (DOCKER, 'grep -F \'"volume.isNative" : "r"\' >/dev/null'),
@@ -472,7 +472,13 @@ def verify_runtime_layout(files, gate=GATE):
actual = [line for line in files[DOCKER].splitlines()
if line.startswith('COPY ') and re.search(r'/out/runtime-(?:bin|licenses)/', line)]
stages = re.findall(r'^FROM .+$', files[DOCKER], re.M)
+ bundle_stages = re.findall(r'^FROM ghcr\.io/pasturestack/ubuntu-security-backports:v0\.1\.0@sha256:'
+ r'[0-9a-f]{64} AS ubuntu_security_backports$',
+ files[DOCKER], re.M)
+ if len(bundle_stages) != 1:
+ raise AssertionError('CURRENT_RUNTIME_AGGREGATE_COPY_MISMATCH')
expected_stages = [
+ bundle_stages[0],
'FROM ${ARTIFACT_HELPER_IMAGE} AS temurin_security_runtime',
'FROM ${ARTIFACT_HELPER_IMAGE} AS console_broker_build',
'FROM scratch AS component_input',
@@ -600,15 +606,15 @@ def verify(files, gate=GATE):
pipeline = r'(?:^|[;\n])\s*' + re.escape(marker) + r'(?=\s*(?:;|\n|$))'
if len(re.findall(pipeline, source)) != 1:
raise AssertionError('CURRENT_CATALOG_HASH_BYPASS')
- if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.551"' not in gate:
+ if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.552"' not in gate:
raise AssertionError('CURRENT_VEX_GATE_PIN_MISMATCH')
- if '"$vendor_pending_fixture" v1.6.551 >/dev/null' not in gate:
+ if '"$vendor_pending_fixture" v1.6.552 >/dev/null' not in gate:
raise AssertionError('CURRENT_VENDOR_GATE_PIN_MISMATCH')
- if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.551':
+ if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.552':
raise AssertionError('CURRENT_VEX_RELEASE_MISMATCH')
- if json.loads(files[VENDOR]).get('release') != 'v1.6.551':
+ if json.loads(files[VENDOR]).get('release') != 'v1.6.552':
raise AssertionError('CURRENT_VENDOR_RELEASE_MISMATCH')
- if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.551 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ' not in gate:
+ if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.552 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ' not in gate:
raise AssertionError('CURRENT_SUMMARY_MISMATCH')
verify_incremental_contract(files, gate)
verify_load_balancer_contract(files, gate)
@@ -623,11 +629,11 @@ def previous_gate(gate=GATE):
if stale(marker) != marker:
replacements["'" + marker + "' "] = "'" + stale(marker) + "' "
replacements.update({
- 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.551"':
+ 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.552"':
'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.521"',
- '"$vendor_pending_fixture" v1.6.551 >/dev/null':
+ '"$vendor_pending_fixture" v1.6.552 >/dev/null':
'"$vendor_pending_fixture" v1.6.521 >/dev/null',
- 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.551 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ':
+ 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.552 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ':
'SERVER_API_EXPLORER_PATCH_OK release=v1.6.521 base=v1.6.460 engine=0.183.340 web_console=1.6.181 catalog_service=0.20.12 ',
})
for marker, stale_marker in replacements.items():
@@ -640,7 +646,7 @@ def previous_gate(gate=GATE):
class Tests(unittest.TestCase):
def test_current_agent_defaults_keep_explicit_operator_overrides(self):
- image = 'ghcr.io/pasturestack/node-agent:v1.2.32'
+ image = 'ghcr.io/pasturestack/node-agent:v1.2.33'
final_stage = FILES[DOCKER].rsplit('\nFROM ', 1)[-1]
for name in ('DEFAULT_CATTLE_AGENT_IMAGE', 'DEFAULT_CATTLE_BOOTSTRAP_REQUIRED_IMAGE'):
self.assertEqual(final_stage.splitlines().count('ENV ' + name + '=' + image), 1)
@@ -1043,7 +1049,7 @@ def test_nineteen_component_inputs_keep_exact_sha_fetch_and_separate_jdk_stage(s
block = FILES[BUILD].split(' component_assets=(\n', 1)[1].split(' )', 1)[0]
self.assertEqual([line.strip() for line in block.splitlines()], EXACT_COMPONENT_INPUTS)
self.assertEqual(len(EXACT_COMPONENT_INPUTS), 19)
- self.assertEqual(len(re.findall(r'^FROM ', FILES[DOCKER], re.M)), 6)
+ self.assertEqual(len(re.findall(r'^FROM ', FILES[DOCKER], re.M)), 7)
self.assertIn('FROM scratch AS component_input', FILES[DOCKER])
final = FILES[DOCKER].rsplit('FROM ${BASE_IMAGE}', 1)[1]
# The already-flattened base contributes one real layer too.
@@ -1108,7 +1114,7 @@ def test_freetype_official_deb_and_library_checks_fail_closed(self):
def check(files):
self.assertEqual(files[DOCKER].splitlines().count('ARG FREETYPE_PACKAGE_VERSION=' + version), 2)
self.assertEqual(len(re.findall(archive, files[DOCKER], re.M)), 1)
- self.assertEqual(len(re.findall(r'^FROM ', files[DOCKER], re.M)), 6)
+ self.assertEqual(len(re.findall(r'^FROM ', files[DOCKER], re.M)), 7)
self.assertIn('FROM scratch AS component_input', files[DOCKER])
for name, variable in paths:
for marker in engine_gate_markers(GATE, variable):
@@ -1220,7 +1226,7 @@ def test_catalog_both_stages_build_defaults_and_pending_reject(self):
self.assertLess(FILES[BUILD].index(actual_guard), FILES[BUILD].index('docker buildx build'))
self.assertIsNotNone(re.fullmatch(pattern, value))
self.assertIsNone(re.fullmatch(pattern, 'PENDING_OFFICIAL_SOURCE'))
- self.assertEqual(len(re.findall(r'^FROM ', FILES[DOCKER], re.M)), 6)
+ self.assertEqual(len(re.findall(r'^FROM ', FILES[DOCKER], re.M)), 7)
self.assertIn('FROM scratch AS component_input', FILES[DOCKER])
self.assertIn('SERVER_COMPONENT_RELEASE_COORDINATES_PENDING', GATE)
self.assertIn('PENDING_OFFICIAL_(SOURCE|ARCHIVE|BINARY|SQLITE_BINARY)', GATE)
@@ -1311,7 +1317,7 @@ def test_historical_engine340_cannot_be_packaged(self):
def test_engine352_actual_war_producer_and_previous351_350_boundary(self):
candidate = (REPO / 'server/build-component-candidate.sh').read_text(encoding='utf-8')
- self.assertIn('export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.551}', candidate)
+ self.assertIn('export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.552}', candidate)
self.assertIn('export ORCHESTRATION_ENGINE_RELEASE_TAG=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.354}', candidate)
for old in (
{'0.183.354': '0.183.351',
@@ -1619,7 +1625,7 @@ def test_machine_actual_binary_package_and_retired_boundary_are_verified(self):
def test_current_readme_status_specific_prose_does_not_block_publication_transition(self):
common = GATE.split('for current_readme_marker in ', 1)[1].split('; do', 1)[0]
self.assertNotIn('candidate', common)
- self.assertNotIn('v1.6.551', common)
+ self.assertNotIn('v1.6.552', common)
self.assertIn("grep -Fqi 'keep all three named volumes' README.md", GATE)
status = GATE.split("if grep -Fxq '## Current source candidate' README.md; then", 1)[1]
candidate, published = status.split("elif grep -Fxq '## Current release' README.md; then", 1)
@@ -1628,7 +1634,7 @@ def test_current_readme_status_specific_prose_does_not_block_publication_transit
self.assertIn('This candidate is not yet available', candidate)
self.assertIn('SERVER_CURRENT_PUBLISHED_README_MISSING', published)
self.assertNotIn('candidate', published)
- self.assertNotIn('v1.6.551', published)
+ self.assertNotIn('v1.6.552', published)
if __name__ == '__main__':
diff --git a/scripts/test-server-backport-consumer-contract.py b/scripts/test-server-backport-consumer-contract.py
new file mode 100644
index 0000000000..226bc21c04
--- /dev/null
+++ b/scripts/test-server-backport-consumer-contract.py
@@ -0,0 +1,106 @@
+"""Focused source contracts only: no image build, install, scan or publication."""
+from pathlib import Path
+import re
+import unittest
+
+ROOT = Path(__file__).resolve().parents[1]
+DOCKER = 'server/Dockerfile.web-compose-release'
+BUILD = 'server/build-api-explorer-patch-image.sh'
+PUBLISH = '.github/workflows/publish-current-server.yml'
+FILES = {name: (ROOT / name).read_text(encoding='utf-8') for name in (DOCKER, BUILD, PUBLISH)}
+
+
+def require(value, message):
+ if not value:
+ raise AssertionError(message)
+
+
+def verify(files):
+ docker, build, publisher = (files[name] for name in (DOCKER, BUILD, PUBLISH))
+ image = re.findall(r'^FROM (ghcr.io/pasturestack/ubuntu-security-backports:v0\.1\.0@sha256:'
+ r'[0-9a-f]{64}) AS ubuntu_security_backports$', docker, re.M)
+ require(len(image) == 1 and docker.count("'" + image[0] + "'") == 1, 'Bundle image/retained identity mismatch')
+ native = docker.split('AS runtime_security_packages', 1)[1].split('FROM ${BASE_IMAGE}', 1)[0]
+ require('build-pcre2-backport.py' not in docker and 'build-essential' not in native,
+ 'Server consumer must not compile qualified PCRE2 again')
+ for marker in ['source=/out,target=/bundle,ro',
+ 'sha256sum --check --status SHA256SUMS', 'dpkg-deb --extract "${archive}" /tmp/pcre2-runtime',
+ '/bundle/provenance/pcre2/provenance.json', '/bundle/provenance/pcre2/pcre2.lock.json',
+ 'sha256sum usr/lib/x86_64-linux-gnu/libpcre2-8.so.0 usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3']:
+ require(marker in native, 'Missing producer-derived PCRE compatibility receipt: ' + marker)
+ for marker in ['ARG GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4\n',
+ 'ARG GLIBC_RUNTIME_PACKAGE_VERSION=2.43-2ubuntu2.4+pasturestack.1',
+ 'ENV PASTURESTACK_GLIBC_OFFICIAL_PACKAGE_VERSION=${GLIBC_PACKAGE_VERSION}',
+ 'ENV PASTURESTACK_GLIBC_PACKAGE_VERSION=${GLIBC_RUNTIME_PACKAGE_VERSION}',
+ 'ENV PASTURESTACK_GLIBC_CVE_2026_18374_FIX=official-source-backport']:
+ require(marker in docker, 'Official download/custom runtime version boundary changed')
+ require('PASTURESTACK_GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4+pasturestack.1' in build,
+ 'Runtime readback still expects the original libc version')
+ runtime = docker.split('source=/out,target=/opt/pasturestack/ubuntu-security-backports,ro', 1)[1]
+ positions = [runtime.index(marker) for marker in [
+ 'sh /tmp/pasturestack-security-input/install-ubuntu-security-updates.sh',
+ 'rm -f /usr/lib/x86_64-linux-gnu/libexpat.so.1',
+ '"${data}/install-existing-backports" --install-in-docker-build',
+ '"${data}/install-existing-backports" --verify-installed',
+ '\n sh /usr/local/libexec/pasturestack/verify-pcre2-runtime.sh',
+ 'libpng_linkage="$(ldd -r /usr/lib/x86_64-linux-gnu/libpng16.so.16']]
+ require(positions == sorted(positions), 'Final libraries/install/verification order changed')
+ require('for package in rust-coreutils jq libjq1 libc6-dev libc-dev-bin libpcre2-dev libp11-kit-dev libxrender-dev;' in runtime,
+ 'Server absent-package boundary is missing')
+ require('install -m 0644 pcre2-backport-provenance.json pcre2-backport.lock.json pcre2-runtime.sha256 /usr/share/pasturestack/security/' in runtime,
+ 'Legacy verifier metadata is not installed')
+ require('sha256sum manifest.json qualification.json openvex.json source-inputs.lock.json install.json verify.json bundle-image.txt > SHA256SUMS' in runtime,
+ 'Seven retained consumer evidence files are not bound')
+ require('/opt/java/openjdk/bin/java -version 2>&1 | grep -F' in runtime, 'Final Java/libc readback is missing')
+ raw_end = publisher.index('--output /evidence/server-security-scan-raw.json')
+ raw = publisher[publisher.rfind(' docker run --rm', 0, raw_end):raw_end]
+ require('--scanners vuln,secret --list-all-pkgs' in raw and not any(
+ option in raw for option in ['--vex', '--offline-scan', '--network none', '--ignorefile']),
+ 'Original full raw scanner capability changed')
+ require('--bundle-checksums' in publisher, 'Producer checksum binding is missing')
+ require(publisher.index('--output /evidence/server.cdx.json') < publisher.index('--bundle-checksums')
+ < publisher.index('--vex /evidence/ubuntu-backports/openvex.json'), 'Producer/SBOM closure must precede native VEX')
+ for marker in ['--bundle-checksums "$evidence/bundle-tools/SHA256SUMS"',
+ '--expected-bundle-digest "${bundle_image##*@}"',
+ '--vex /evidence/server.openvex.json', '--vex /evidence/ubuntu-backports/openvex.json',
+ '"$evidence/ubuntu-backports-evidence.tar"', ' ubuntu-backports-evidence.tar']:
+ require(marker in publisher, 'Missing retained producer/native VEX gate: ' + marker)
+ ledger = publisher.split('bash source/scripts/validate-vendor-pending-findings.sh', 1)[1].split('\n\n', 1)[0]
+ require('ubuntu-backports-evidence.tar' not in ledger
+ and '"$evidence/server-vulnerabilities-unresolved.tsv"' in ledger,
+ 'Original vendor-pending validation arguments changed')
+ release = publisher.split('gh release create "$RELEASE_TAG"', 1)[1].split('\n\n', 1)[0]
+ require('"$evidence/ubuntu-backports-evidence.tar"' in release, 'Producer evidence is not a release asset')
+
+
+class ServerBackportContract(unittest.TestCase):
+ def test_current_source_contract(self):
+ verify(FILES)
+
+ def test_source_compile_or_absent_package_boundary_regression_is_rejected(self):
+ for old, new in [('target=/bundle,ro', 'target=/bundle,ro # build-pcre2-backport.py'),
+ ('for package in rust-coreutils jq libjq1 libc6-dev', 'for package in libc6-dev')]:
+ with self.subTest(old=old), self.assertRaises(AssertionError):
+ verify(dict(FILES, **{DOCKER: FILES[DOCKER].replace(old, new)}))
+
+ def test_official_runtime_version_conflation_is_rejected(self):
+ with self.assertRaises(AssertionError):
+ verify(dict(FILES, **{DOCKER: FILES[DOCKER].replace(
+ 'ENV PASTURESTACK_GLIBC_PACKAGE_VERSION=${GLIBC_RUNTIME_PACKAGE_VERSION}',
+ 'ENV PASTURESTACK_GLIBC_PACKAGE_VERSION=${GLIBC_PACKAGE_VERSION}')}))
+
+ def test_unbound_vex_or_narrowed_raw_scan_is_rejected(self):
+ for old, new in [('--bundle-checksums', '--candidate-checksums'),
+ ('--scanners vuln,secret --list-all-pkgs', '--scanners vuln,secret --list-all-pkgs --offline-scan')]:
+ with self.subTest(old=old), self.assertRaises(AssertionError):
+ verify(dict(FILES, **{PUBLISH: FILES[PUBLISH].replace(old, new)}))
+
+ def test_unresolved_bundle_identity_is_rejected(self):
+ broken = re.sub(r'(ubuntu-security-backports:v0\.1\.0@sha256:)[0-9a-f]{64}',
+ r'\1__BUNDLE_DIGEST_PENDING__', FILES[DOCKER])
+ with self.assertRaises(AssertionError):
+ verify(dict(FILES, **{DOCKER: broken}))
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/server/Dockerfile.web-compose-release b/server/Dockerfile.web-compose-release
index 3521a21089..9f04243ae9 100644
--- a/server/Dockerfile.web-compose-release
+++ b/server/Dockerfile.web-compose-release
@@ -3,6 +3,8 @@ ARG ARTIFACT_HELPER_IMAGE=golang:1.27.3-bookworm@sha256:972b4110ba0f47539a975c31
ARG UBUNTU_SECURITY_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b
ARG BASE_IMAGE=ghcr.io/pasturestack/server:v1.6.460@sha256:c855af8aea232dacc5bb6df68e2271d482c68b53c43ab0c108ec19118f5ab403
+FROM ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a AS ubuntu_security_backports
+
FROM ${ARTIFACT_HELPER_IMAGE} AS temurin_security_runtime
ADD --checksum=sha256:68982529869257d0ee6b824ec5aa270ff8b118bb438670da0b97ebdab178d7fb \
https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.5%2B8/OpenJDK25U-jdk_x64_linux_hotspot_25.0.5_8.tar.gz \
@@ -547,7 +549,6 @@ ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /build
COPY scripts/download-ubuntu-security-updates.sh /usr/local/libexec/pasturestack/download-ubuntu-security-updates.sh
COPY security/ubuntu-security-packages.lock /build/ubuntu-security-packages.lock
-COPY security/ubuntu-backports/build-pcre2-backport.py security/ubuntu-backports/pcre2.lock.json /build/ubuntu-backports/
ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd \
https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb \
/tmp/ca-certificates.deb
@@ -656,23 +657,26 @@ RUN set -eux; \
RUN sh /usr/local/libexec/pasturestack/download-ubuntu-security-updates.sh \
/build/ubuntu-security-packages.lock /etc/ssl/certs/ca-certificates.crt /out/security-updates
-# Keep Canonical binaries and first-party source rebuilds separate. The latter
-# retains Ubuntu's packaging/ABI and applies only upstream's approved backports.
-RUN set -eux; \
- apt-get install -y --no-install-recommends build-essential debhelper dpkg-dev dh-autoreconf fakeroot patch binutils python3; \
- python3 /build/ubuntu-backports/build-pcre2-backport.py \
- --output-dir /out/pcre2 --work-dir /build/pcre2-backport-work --jobs 2; \
- mkdir -p /tmp/pcre2-runtime; \
+# The immutable bundle was qualified by the released Server551 producer. This
+# builder only derives the legacy PCRE runtime receipt from those exact deb bytes.
+RUN --mount=from=ubuntu_security_backports,source=/out,target=/bundle,ro \
+ set -eux; \
+ cd /bundle; \
+ sha256sum --check --status SHA256SUMS; \
+ mkdir -p /out /tmp/pcre2-runtime; \
for package in libpcre2-8-0 libpcre2-posix3; do \
- archive="/out/pcre2/${package}_${PCRE2_PACKAGE_VERSION}_amd64.deb"; \
+ archive="/bundle/debs/${package}_${PCRE2_PACKAGE_VERSION}_amd64.deb"; \
test "$(dpkg-deb -f "${archive}" Package)" = "${package}"; \
test "$(dpkg-deb -f "${archive}" Version)" = "${PCRE2_PACKAGE_VERSION}"; \
+ test "$(dpkg-deb -f "${archive}" Architecture)" = amd64; \
dpkg-deb --extract "${archive}" /tmp/pcre2-runtime; \
done; \
cd /tmp/pcre2-runtime; \
sha256sum usr/lib/x86_64-linux-gnu/libpcre2-8.so.0 usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3 > /out/pcre2-runtime.sha256; \
+ cp /bundle/provenance/pcre2/provenance.json /out/pcre2-backport-provenance.json; \
+ cp /bundle/provenance/pcre2/pcre2.lock.json /out/pcre2-backport.lock.json; \
cd /out; \
- sha256sum pcre2/*.deb pcre2/provenance.json pcre2/pcre2.lock.json pcre2-runtime.sha256 >> SHA256SUMS
+ sha256sum pcre2-backport-provenance.json pcre2-backport.lock.json pcre2-runtime.sha256 >> SHA256SUMS
FROM ${BASE_IMAGE}
@@ -691,7 +695,7 @@ ARG PER_HOST_SUBNET_VERSION=0.2.5
ARG PER_HOST_SUBNET_PRODUCER_COMMIT=e102e0bb38d84b0a9cbf87e5458cf1bc74d01904
ARG PER_HOST_SUBNET_ARCHIVE_SHA256=7237e83e96a64e7f3de64eb86bff9721c4e879c35cda9e9de1e4cb1ba225f298
ARG PER_HOST_SUBNET_PROVENANCE_SHA256=afa2db0e0755c6bd3ee28e111937c521a6d84e6d71d563e99202cf4693000eaf
-ARG SERVER_RELEASE_TAG=v1.6.551
+ARG SERVER_RELEASE_TAG=v1.6.552
ARG API_EXPLORER_RELEASE_TAG=v1.1.20
ARG API_EXPLORER_ARTIFACT_SHA256=c989ee12a518f6d9d2b23b1f17f48d781aacf6ec31fb65291dd7de9b1661badb
ARG API_EXPLORER_COMMIT=a586f0dc0aa50df579049a8d4bf26f04400fdf75
@@ -714,6 +718,7 @@ ARG FREETYPE_PACKAGE_VERSION=2.14.2+dfsg-1ubuntu0.2
ARG LIBPNG_PACKAGE_VERSION=1.6.57-1ubuntu0.1
ARG CA_CERTIFICATES_PACKAGE_VERSION=20260816ubuntu0.26.04.1
ARG GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4
+ARG GLIBC_RUNTIME_PACKAGE_VERSION=2.43-2ubuntu2.4+pasturestack.1
ARG PERL_PACKAGE_VERSION=5.40.1-7ubuntu0.3
ARG PCRE2_PACKAGE_VERSION=10.46-1build1+pasturestack.1
ARG WEB_CONSOLE_RELEASE_TAG=1.6.195
@@ -772,7 +777,8 @@ COPY scripts/install-ubuntu-security-updates.sh security/ubuntu-security-package
# Keep the official zlib backport active: ldconfig otherwise prefers the old
# upstream 1.3.2 library, which is affected by USN-8954-1 / CVE-2026-85091.
# Retain the base image's removed, unused Git DAV/Expat surface after updating.
-RUN set -eux; \
+RUN --mount=from=ubuntu_security_backports,source=/out,target=/opt/pasturestack/ubuntu-security-backports,ro \
+ set -eux; \
printf '%s\n' "${WEB_CONSOLE_COMMIT}" | grep -Eq '^[0-9a-f]{40}$'; \
cd /tmp/pasturestack-runtime-security; \
install -D -m 0755 /tmp/pasturestack-security-input/verify-pcre2-runtime.sh /usr/local/libexec/pasturestack/verify-pcre2-runtime.sh; \
@@ -781,17 +787,8 @@ RUN set -eux; \
dpkg -i packages/*.deb; \
install -D -m 0644 /tmp/pasturestack-security-input/ubuntu-security-packages.lock /usr/share/pasturestack/security/ubuntu-security-packages.lock; \
sh /tmp/pasturestack-security-input/install-ubuntu-security-updates.sh /usr/share/pasturestack/security/ubuntu-security-packages.lock /tmp/pasturestack-runtime-security/security-updates; \
- dpkg --path-include=/usr/share/doc/libpcre2-8-0/README.Debian -i pcre2/*.deb; \
- for package in libpcre2-8-0 libpcre2-posix3; do \
- test "$(dpkg-query -W -f='${Version}' "${package}")" = "${PCRE2_PACKAGE_VERSION}"; \
- test -z "$(dpkg --verify "${package}")"; \
- done; \
- install -D -m 0644 pcre2/provenance.json /usr/share/pasturestack/security/pcre2-backport-provenance.json; \
- install -D -m 0644 pcre2/pcre2.lock.json /usr/share/pasturestack/security/pcre2-backport.lock.json; \
- install -D -m 0644 pcre2-runtime.sha256 /usr/share/pasturestack/security/pcre2-runtime.sha256; \
rm -f /usr/lib/x86_64-linux-gnu/libz.so.1.3.2; \
ldconfig; \
- sh /usr/local/libexec/pasturestack/verify-pcre2-runtime.sh; \
test "$(readlink -f /usr/lib/x86_64-linux-gnu/libz.so.1)" = /usr/lib/x86_64-linux-gnu/libz.so.1.3.1; \
zlib_integrity="$(dpkg --verify zlib1g)"; \
test -z "${zlib_integrity}"; \
@@ -799,6 +796,29 @@ RUN set -eux; \
test ! -e /usr/lib/git-core/git-http-push; \
rm -f /usr/lib/x86_64-linux-gnu/libexpat.so.1 /usr/lib/x86_64-linux-gnu/libexpat.so.1.11.2; \
ldconfig; \
+ data=/opt/pasturestack/ubuntu-security-backports; \
+ evidence=/usr/share/pasturestack/security/ubuntu-backports; \
+ mkdir -p "${evidence}"; \
+ for package in rust-coreutils jq libjq1 libc6-dev libc-dev-bin libpcre2-dev libp11-kit-dev libxrender-dev; do \
+ test "$(dpkg-query -W -f='${db:Status-Status}' "${package}" 2>/dev/null || true)" != installed; \
+ done; \
+ "${data}/install-existing-backports" --install-in-docker-build > "${evidence}/install.json"; \
+ "${data}/install-existing-backports" --verify-installed > "${evidence}/verify.json"; \
+ for document in manifest.json qualification.json openvex.json source-inputs.lock.json; do \
+ install -m 0644 "${data}/${document}" "${evidence}/${document}"; \
+ done; \
+ printf '%s\n' 'ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > "${evidence}/bundle-image.txt"; \
+ (cd "${evidence}" && sha256sum manifest.json qualification.json openvex.json source-inputs.lock.json install.json verify.json bundle-image.txt > SHA256SUMS); \
+ for package in libc6 libc-bin libc-gconv-modules-extra; do \
+ test "$(dpkg-query -W -f='${Version}' "${package}")" = "${GLIBC_RUNTIME_PACKAGE_VERSION}"; \
+ done; \
+ for package in libpcre2-8-0 libpcre2-posix3; do \
+ test "$(dpkg-query -W -f='${Version}' "${package}")" = "${PCRE2_PACKAGE_VERSION}"; \
+ test -z "$(dpkg --verify "${package}")"; \
+ done; \
+ install -m 0644 pcre2-backport-provenance.json pcre2-backport.lock.json pcre2-runtime.sha256 /usr/share/pasturestack/security/; \
+ sh /usr/local/libexec/pasturestack/verify-pcre2-runtime.sh; \
+ /opt/java/openjdk/bin/java -version 2>&1 | grep -F '25.0.5'; \
install -D -m 0644 security-updates/manifest.tsv /usr/share/pasturestack/security/ubuntu-security-updates-manifest.tsv; \
rm -rf /tmp/pasturestack-security-input; \
test "$(dpkg-query -W -f='${Version}' ca-certificates)" = "${CA_CERTIFICATES_PACKAGE_VERSION}"; \
@@ -1191,8 +1211,8 @@ ENV CATTLE_RANCHER_SERVER_VERSION=${SERVER_RELEASE_TAG}
ENV CATTLE_RANCHER_CLI_VERSION=v0.6.20
ENV PASTURESTACK_CLI_RELEASE_BASE_URL=https://github.com/PastureStack/cli/releases/download
ENV CATTLE_CATTLE_VERSION=${ORCHESTRATION_ENGINE_RELEASE_TAG}
-ENV DEFAULT_CATTLE_AGENT_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.32
-ENV DEFAULT_CATTLE_BOOTSTRAP_REQUIRED_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.32
+ENV DEFAULT_CATTLE_AGENT_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.33
+ENV DEFAULT_CATTLE_BOOTSTRAP_REQUIRED_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.33
ENV DEFAULT_CATTLE_LB_INSTANCE_IMAGE=ghcr.io/pasturestack/load-balancer-service:v0.9.33
ENV DEFAULT_CATTLE_LB_INSTANCE_IMAGE_UUID=docker:ghcr.io/pasturestack/load-balancer-service:v0.9.33
ENV CATTLE_API_UI_VERSION=1.1.20
@@ -1221,8 +1241,9 @@ ENV PASTURESTACK_CURL_PACKAGE_VERSION=${CURL_PACKAGE_VERSION}
ENV PASTURESTACK_OPENSSL_VERSION=3.5.5
ENV PASTURESTACK_OPENSSL_PACKAGE_VERSION=${OPENSSL_PACKAGE_VERSION}
ENV PASTURESTACK_OPENSSL_SECURITY_SNAPSHOT=${UBUNTU_RUNTIME_SECURITY_SNAPSHOT}
-ENV PASTURESTACK_GLIBC_CVE_2026_18374_FIX=not-in-execute-path
-ENV PASTURESTACK_GLIBC_PACKAGE_VERSION=${GLIBC_PACKAGE_VERSION}
+ENV PASTURESTACK_GLIBC_CVE_2026_18374_FIX=official-source-backport
+ENV PASTURESTACK_GLIBC_OFFICIAL_PACKAGE_VERSION=${GLIBC_PACKAGE_VERSION}
+ENV PASTURESTACK_GLIBC_PACKAGE_VERSION=${GLIBC_RUNTIME_PACKAGE_VERSION}
ENV PASTURESTACK_PERL_PACKAGE_VERSION=${PERL_PACKAGE_VERSION}
ENV PASTURESTACK_LIBDBI_PERL_PACKAGE_VERSION=${LIBDBI_PERL_PACKAGE_VERSION}
ENV PASTURESTACK_FREETYPE_PACKAGE_VERSION=${FREETYPE_PACKAGE_VERSION}
@@ -1280,6 +1301,6 @@ ENV PASTURESTACK_VSPHERE_CLI_BUNDLE_VERSION=${VSPHERE_CLI_BUNDLE_VERSION}
ENV PASTURESTACK_VSPHERE_CLI_BUNDLE_COMMIT=${VSPHERE_CLI_BUNDLE_COMMIT}
ENV PASTURESTACK_VSPHERE_CLI_BUNDLE_ARCHIVE_SHA256=${VSPHERE_CLI_BUNDLE_ARCHIVE_SHA256}
ENV PASTURESTACK_GOVC_BINARY_SHA256=${GOVC_BINARY_SHA256}
-ENV PASTURESTACK_CATALOG_COMMIT=070e08d8f85ffc2779a0f97eb4e356c857812d34
-ENV DEFAULT_CATTLE_CATALOG_URL='{"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"070e08d8f85ffc2779a0f97eb4e356c857812d34"}}}'
-ENV CATTLE_CATALOG_URL='{"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"070e08d8f85ffc2779a0f97eb4e356c857812d34"}}}'
+ENV PASTURESTACK_CATALOG_COMMIT=72b0d9767306baf7091fca021d0a2b6bdbca5d51
+ENV DEFAULT_CATTLE_CATALOG_URL='{"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"72b0d9767306baf7091fca021d0a2b6bdbca5d51"}}}'
+ENV CATTLE_CATALOG_URL='{"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"72b0d9767306baf7091fca021d0a2b6bdbca5d51"}}}'
diff --git a/server/build-api-explorer-patch-image.sh b/server/build-api-explorer-patch-image.sh
index ad4c6278bc..4903b96c48 100755
--- a/server/build-api-explorer-patch-image.sh
+++ b/server/build-api-explorer-patch-image.sh
@@ -14,7 +14,7 @@ if [[ -n "$(git status --porcelain --untracked-files=normal)" ]]; then
fi
revision=${PASTURESTACK_SERVER_REVISION:-$(git rev-parse HEAD)}
-server_release_tag=${SERVER_RELEASE_TAG:-v1.6.551}
+server_release_tag=${SERVER_RELEASE_TAG:-v1.6.552}
source_date_epoch=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)}
base_image=${BASE_IMAGE:-ghcr.io/pasturestack/server:v1.6.460@sha256:c855af8aea232dacc5bb6df68e2271d482c68b53c43ab0c108ec19118f5ab403}
orchestration_engine_release_base_url=${ORCHESTRATION_ENGINE_RELEASE_BASE_URL:-https://github.com/PastureStack/orchestration-engine/releases/download}
@@ -130,7 +130,7 @@ usage_telemetry_agent_archive_sha256=${USAGE_TELEMETRY_AGENT_ARCHIVE_SHA256:-6d4
usage_telemetry_agent_binary_sha256=${USAGE_TELEMETRY_AGENT_BINARY_SHA256:-90ed65f689c91801abd0cf482211c96a01e7c63ef695e4faecdf4713eff730be}
supported_docker_range='~v1.12.3 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v17.12.0 || ~v18.03.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.2 || v24.0.9 || >=v29.4.1 <=v29.7.2 || v29.8.0'
newest_docker_version=v29.8.0
-image=${IMAGE:-pasturestack-validation/server:v1.6.551}
+image=${IMAGE:-pasturestack-validation/server:v1.6.552}
build_options=()
[[ "$revision" =~ ^[0-9a-f]{40}$ ]]
@@ -399,8 +399,8 @@ for marker in \
PASTURESTACK_CLI_RELEASE_BASE_URL=https://github.com/PastureStack/cli/releases/download \
CATTLE_API_UI_VERSION=1.1.20 \
CATTLE_CATTLE_VERSION="${orchestration_engine_release_tag}" \
- DEFAULT_CATTLE_AGENT_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.32 \
- DEFAULT_CATTLE_BOOTSTRAP_REQUIRED_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.32 \
+ DEFAULT_CATTLE_AGENT_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.33 \
+ DEFAULT_CATTLE_BOOTSTRAP_REQUIRED_IMAGE=ghcr.io/pasturestack/node-agent:v1.2.33 \
DEFAULT_CATTLE_LB_INSTANCE_IMAGE=ghcr.io/pasturestack/load-balancer-service:v0.9.33 \
DEFAULT_CATTLE_LB_INSTANCE_IMAGE_UUID=docker:ghcr.io/pasturestack/load-balancer-service:v0.9.33 \
RC16_GO_AGENT_VERSION="${node_agent_version}" \
@@ -423,8 +423,9 @@ for marker in \
PASTURESTACK_UBUNTU_SECURITY_REFRESH=2026-10-23 \
PASTURESTACK_CURL_SECURITY_SNAPSHOT=20261002T000000Z \
PASTURESTACK_CURL_PACKAGE_VERSION=8.18.0-1ubuntu2.8 \
- PASTURESTACK_GLIBC_CVE_2026_18374_FIX=not-in-execute-path \
- PASTURESTACK_GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4 \
+ PASTURESTACK_GLIBC_CVE_2026_18374_FIX=official-source-backport \
+ PASTURESTACK_GLIBC_OFFICIAL_PACKAGE_VERSION=2.43-2ubuntu2.4 \
+ PASTURESTACK_GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4+pasturestack.1 \
PASTURESTACK_PERL_PACKAGE_VERSION=5.40.1-7ubuntu0.3 \
PASTURESTACK_LIBDBI_PERL_PACKAGE_VERSION=1.647-1ubuntu0.26.04.4 \
PASTURESTACK_FREETYPE_PACKAGE_VERSION=2.14.2+dfsg-1ubuntu0.2 \
@@ -495,9 +496,9 @@ for marker in \
PASTURESTACK_MACHINE_DRIVER_BINARY_SHA256="${machine_driver_binary_sha256}" \
PASTURESTACK_PACKET_DRIVER_BINARY_SHA256="${packet_driver_binary_sha256}" \
PASTURESTACK_DOCKER_SUPPORT_POLICY=2026-08-28 \
- PASTURESTACK_CATALOG_COMMIT=070e08d8f85ffc2779a0f97eb4e356c857812d34 \
- 'DEFAULT_CATTLE_CATALOG_URL={"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"070e08d8f85ffc2779a0f97eb4e356c857812d34"}}}' \
- 'CATTLE_CATALOG_URL={"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"070e08d8f85ffc2779a0f97eb4e356c857812d34"}}}'; do
+ PASTURESTACK_CATALOG_COMMIT=72b0d9767306baf7091fca021d0a2b6bdbca5d51 \
+ 'DEFAULT_CATTLE_CATALOG_URL={"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"72b0d9767306baf7091fca021d0a2b6bdbca5d51"}}}' \
+ 'CATTLE_CATALOG_URL={"catalogs":{"pasturestack":{"url":"https://github.com/PastureStack/catalog-templates.git","branch":"main","pinnedCommit":"72b0d9767306baf7091fca021d0a2b6bdbca5d51"}}}'; do
test "$(grep -Fxc "$marker" <<<"$image_environment")" = 1
done
@@ -968,7 +969,7 @@ EOF
done
for glibc_package in libc6 libc-bin libc-gconv-modules-extra; do
test "$(dpkg-query -W -f='"'"'${Version}'"'"' "${glibc_package}")" = \
- "2.43-2ubuntu2.4"
+ "2.43-2ubuntu2.4+pasturestack.1"
done
for perl_package in libperl5.40 perl perl-base perl-modules-5.40; do
test "$(dpkg-query -W -f='"'"'${Version}'"'"' "${perl_package}")" = \
diff --git a/server/build-component-candidate.sh b/server/build-component-candidate.sh
index 8898c7dc88..4261aec0dc 100755
--- a/server/build-component-candidate.sh
+++ b/server/build-component-candidate.sh
@@ -18,7 +18,7 @@ for name in "${required[@]}"; do
case "$name" in *_COMMIT) [[ ${!name} =~ ^[0-9a-f]{40}$ ]] ;; *) [[ ${!name} =~ ^[0-9a-f]{64}$ ]] ;; esac
export "$name"
done
-export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.551}
+export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.552}
export ORCHESTRATION_ENGINE_RELEASE_TAG=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.354}
export ORCHESTRATION_ENGINE_ARTIFACT=cattle.jar
export WEB_CONSOLE_RELEASE_TAG=${WEB_CONSOLE_RELEASE_TAG:-1.6.195}
diff --git a/server/security/openvex.json b/server/security/openvex.json
index ebed4a821f..7ff7aa0f6a 100644
--- a/server/security/openvex.json
+++ b/server/security/openvex.json
@@ -1,9 +1,9 @@
{
"@context": "https://openvex.dev/ns/v0.2.0",
- "@id": "https://github.com/PastureStack/server/security/openvex/v1.6.551",
+ "@id": "https://github.com/PastureStack/server/security/openvex/v1.6.552",
"author": "PastureStack Security",
"timestamp": "2026-10-25T00:00:00Z",
- "version": 63,
+ "version": 64,
"statements": [
{
"vulnerability": {
diff --git a/server/security/vendor-pending.json b/server/security/vendor-pending.json
index f20a26df74..29763330a2 100644
--- a/server/security/vendor-pending.json
+++ b/server/security/vendor-pending.json
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
- "release": "v1.6.551",
+ "release": "v1.6.552",
"generatedAt": "2026-10-25T00:00:00Z",
"policy": {
"allowedSeverities": [
@@ -111,29 +111,6 @@
"vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-88648.json",
"notes": "Canonical lists Ubuntu 26.04 gnutls28 as vulnerable with its fix deferred. The official OSV range has no fixed event and the current runtime scan has an empty FixedVersion. Retain the raw finding; do not claim remediation or not_affected."
},
- {
- "vulnerabilityId": "CVE-2026-89092",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "source": "https://ubuntu.com/security/CVE-2026-89092",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libc-bin",
- "installedVersion": "2.43-2ubuntu2.4"
- },
- {
- "name": "libc-gconv-modules-extra",
- "installedVersion": "2.43-2ubuntu2.4"
- },
- {
- "name": "libc6",
- "installedVersion": "2.43-2ubuntu2.4"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89092.json"
- },
{
"vulnerabilityId": "CVE-2026-95184",
"severity": "MEDIUM",
From d5d9b84cc99096658bdbd458ba34de6f35c68746 Mon Sep 17 00:00:00 2001
From: chen21019 <19357113+chen21019@users.noreply.github.com>
Date: Sun, 25 Oct 2026 08:34:35 +0800
Subject: [PATCH 2/2] Keep candidate installation guidance truthful until
immutable publication
---
COMPATIBILITY.md | 9 +++++----
README.md | 14 ++++++++------
docs/performance/README.md | 7 ++++---
docs/releases/server-1.6.552.md | 6 +++++-
4 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md
index 149bafecf9..dd902dfd5c 100644
--- a/COMPATIBILITY.md
+++ b/COMPATIBILITY.md
@@ -2,11 +2,12 @@
-## Server v1.6.552 runtime backports
+## Server v1.6.552 prepared, not published
-Use the numeric `ghcr.io/pasturestack/server:v1.6.552` image tag. Its exact
-producer, immutable OCI digest, SBOM, original vulnerability report and interpreted
-report are published in the [release assets](https://github.com/PastureStack/server/releases/tag/v1.6.552).
+This source candidate prepares the numeric `ghcr.io/pasturestack/server:v1.6.552`
+image tag. It has not been published. The normal publisher must retain the exact
+producer, OCI digest, SBOM, original vulnerability report and interpreted report
+before the candidate can be used.
The digest-pinned, build-only Ubuntu backport bundle installs only existing
packages. It retains the original package set, selected coreutils provider and
diff --git a/README.md b/README.md
index 6ccdf34477..21497de7ee 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,10 @@ upstream history, authorship, licenses, and notices.
-## Current release
+## Current source candidate
-Server [`v1.6.552`](https://github.com/PastureStack/server/releases/tag/v1.6.552)
-includes Ubuntu security backports from verified upstream releases and accepted
+Server `v1.6.552` is a prepared source candidate, not a published image.
+It includes Ubuntu security backports from verified upstream releases and accepted
fixes. Each image updates only packages already installed, preserving its package
set, coreutils provider, configuration and library interfaces. The compiler stages
remain separate from the final runtime.
@@ -24,8 +24,9 @@ Host API `0.38.8` and Temurin `25.0.5+8` retain their verified inputs.
Node Agent `v1.2.33` uses compatibility base `v0.3.7`; Catalog `v0.3.17`
references the corresponding verified component images.
-See the [release note](docs/releases/server-1.6.552.md),
-[release assets](https://github.com/PastureStack/server/releases/tag/v1.6.552) and
+The published release is [Server v1.6.551](https://github.com/PastureStack/server/releases/tag/v1.6.551),
+with its [publication note](docs/releases/server-1.6.551.md).
+See the [candidate note](docs/releases/server-1.6.552.md) and
[compatibility contract](COMPATIBILITY.md#server-v16552-runtime-backports)
for producer identities, immutable image digests, SBOMs and remaining findings.
@@ -54,7 +55,8 @@ Maintained CLI `0.6.20` propagates rejected requests as nonzero errors. Legacy `
## Quick start
Public GHCR downloads do not require a registry login. Keep all three named volumes.
-The examples below use the numeric `v1.6.552` tag.
+The examples below use the numeric `v1.6.552` tag; run them only after its official release.
+This candidate is not yet available from GHCR.
```sh
docker run -d --name pasturestack-server --restart unless-stopped -p 8080:8080 \
diff --git a/docs/performance/README.md b/docs/performance/README.md
index 6b0f7d035d..e0eb09f4cd 100644
--- a/docs/performance/README.md
+++ b/docs/performance/README.md
@@ -8,8 +8,9 @@ The following example is appropriate as a starting point for a dedicated
16-vCPU host with about 32 GiB of RAM. It is not a universal performance
profile; measure the real workload before increasing it further.
-The example uses the published numeric Server v1.6.549 tag.
-See the [release note](../releases/server-1.6.549.md) and the
+The example prepares the numeric Server v1.6.552 tag; run it only after its official release and image have been published and read back.
+This candidate is not yet available from GHCR.
+See the [candidate note](../releases/server-1.6.552.md) and the
[current release](../../README.md#current-release) for component versions and
release details. Preserve the existing runtime overrides and all three named
volumes during an upgrade or restart.
@@ -20,7 +21,7 @@ evidence is not a performance benchmark; measure the actual workload.
```yaml
services:
pasturestack-server:
- image: ghcr.io/pasturestack/server:v1.6.551
+ image: ghcr.io/pasturestack/server:v1.6.552
restart: unless-stopped
ports:
- "8080:8080"
diff --git a/docs/releases/server-1.6.552.md b/docs/releases/server-1.6.552.md
index 82edbfe388..e444674b74 100644
--- a/docs/releases/server-1.6.552.md
+++ b/docs/releases/server-1.6.552.md
@@ -1,4 +1,8 @@
-# Server v1.6.552
+# Server v1.6.552 candidate
+
+Prepared source only; no Server image or release has been published.
+
+## Verified component inputs
This release consumes the qualified Ubuntu security backports in Server and its
component images. It updates only already-installed packages, preserves the