diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md
index c621ca809a..848d6a6459 100644
--- a/COMPATIBILITY.md
+++ b/COMPATIBILITY.md
@@ -1,5 +1,16 @@
# Compatibility Contract
+## Server v1.6.550 prepared, not published
+
+The source candidate preserves all component versions and operator settings from
+549 while rebuilding the two PCRE2 runtime packages with the upstream's official
+10.46 backports. Original Ubuntu packaging, SONAMEs, JIT and POSIX interfaces are
+retained. First-party package version `10.46-1build1+pasturestack.1` distinguishes
+this rebuild from Canonical binaries. Native upstream tests, exact package
+integrity, ELF hashes and actual consumers are mandatory gates. The published
+installation remains 549 until a new immutable image passes publication.
+See [the candidate note](docs/releases/server-1.6.550.md).
+
## Server v1.6.549 published
diff --git a/README.md b/README.md
index 69a7bdb1f2..c7f9469dab 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,11 @@ See the [549 release note](docs/releases/server-1.6.549.md) and
[compatibility contract](COMPATIBILITY.md#server-v16549-web-console-sbom)
for exact identities, attachment recovery and acceptance boundaries.
+Source candidate `v1.6.550` adds the official PCRE2 10.46 security backports with
+original Ubuntu packaging, ABI checks and native regression tests. It is not yet
+published; installation examples continue to use the verified release above.
+See the [candidate note](docs/releases/server-1.6.550.md).
+
Personal API Keys remain available without an accessible environment. See the [Host guide](docs/hosts/README.md) before enrolling a host.
## API Keys
diff --git a/docs/releases/server-1.6.550.md b/docs/releases/server-1.6.550.md
new file mode 100644
index 0000000000..12c691568c
--- /dev/null
+++ b/docs/releases/server-1.6.550.md
@@ -0,0 +1,48 @@
+# Server v1.6.550 candidate
+
+Prepared source only; no Server image or release has been published.
+
+## Change
+
+Backport the twelve official PCRE2 `release/pcre2-10.46` commits through
+`947809f11e9c9744aa0974b4b630a1ae461733dd` onto the original Ubuntu 26.04
+`10.46-1build1` source package. The source, original packaging and every patch
+are SHA-256 pinned. This is a first-party rebuild, not a Canonical release.
+It avoids unrelated regex, Unicode, JIT or packaging changes.
+
+The two packages `libpcre2-8-0` and `libpcre2-posix3` use version
+`10.46-1build1+pasturestack.1`. The eight fixed vulnerabilities are
+CVE-2026-103111, CVE-2026-86145, CVE-2026-89156, CVE-2026-89157,
+CVE-2026-89158, CVE-2026-89160, CVE-2026-89161 and CVE-2026-89162.
+The [official supported backport list](https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md)
+identifies this exact series. Ubuntu having no updated binary package does not
+mean upstream has no accepted fix.
+
+## Verification and boundaries
+
+The build runs the unchanged Ubuntu packaging tests: `pcre2_jit_test`, `RunTest`
+and `RunGrepTest`, and requires all three PASS records. The output retains source
+locks, build logs, native test logs, compiler flags, package identity and ABI /
+hardening evidence. Runtime checks require exact package versions, zero dpkg
+integrity differences, both library hashes, resolved linkage from grep, Git,
+MariaDB, libselinux and the POSIX wrapper, and C / C.utf8 regex controls.
+
+The inherited image's documentation exclusion is overridden only while installing
+the package-owned `README.Debian`; no integrity exception is added. Raw scanner
+results remain intact. The fixed VEX entries apply only to the exact rebuilt
+package PURLs; the other ten pending Server CVEs remain tracked.
+
+Native library and layered-candidate checks have passed on the QA host. These do
+not establish publication or startup/restart acceptance of the final image.
+Those results and the immutable digest will be recorded after the release gates.
+Web Console 1.6.195, its runtime SBOM and all other component pins are unchanged.
+Unrelated role, UI, OIDC, MFA and feature-matrix PASS evidence is carried forward.
+
+## Upgrade and rollback
+
+Until publication, continue using the numeric `v1.6.549` installation examples.
+Do not overwrite an existing image tag or apply library patches to a running
+container. Upgrade with a new immutable image after verified backups, preserving
+all existing volumes, configuration and the previous container/image for rollback.
+No database migration or operator setting change is introduced by this patch.
+No production deployment is part of this release task.
diff --git a/scripts/check-server-api-explorer-patch.sh b/scripts/check-server-api-explorer-patch.sh
index da0f52f6d5..9b85034012 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.549' \
+ 'ARG SERVER_RELEASE_TAG=v1.6.550' \
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.549}' \
+ 'image=${IMAGE:-pasturestack-validation/server:v1.6.550}' \
SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING
require_marker "$build_script" \
- 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.549}' \
+ 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}' \
SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING
require_marker "$build_script" \
'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"' \
@@ -1691,10 +1691,10 @@ fi
if grep -Fxq '## Current source candidate' README.md; then
for candidate_readme_marker in \
'' \
- 'Server `v1.6.549` is a prepared source candidate, not a published image.' \
+ 'Server `v1.6.550` 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.549'; do
+ 'ghcr.io/pasturestack/server:v1.6.550'; do
require_marker README.md "$candidate_readme_marker" \
SERVER_CURRENT_CANDIDATE_README_MISSING
done
@@ -2300,11 +2300,11 @@ 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.549"
- and (.statements | length) == 48
+ and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"
+ and (.statements | length) == 56
and ([.statements[].vulnerability.name] | length == (unique | length))
and ([.statements[] | select(.status == "fixed") | .vulnerability.name] | sort)
- == ["CVE-2024-52005","CVE-2026-107928","CVE-2026-107929","CVE-2026-12087","CVE-2026-13221","CVE-2026-18798","CVE-2026-27171","CVE-2026-56391","CVE-2026-57432","CVE-2026-57433","CVE-2026-75803","CVE-2026-85091","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-8932","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"]
+ == ["CVE-2024-52005","CVE-2026-103111","CVE-2026-107928","CVE-2026-107929","CVE-2026-12087","CVE-2026-13221","CVE-2026-18798","CVE-2026-27171","CVE-2026-56391","CVE-2026-57432","CVE-2026-57433","CVE-2026-75803","CVE-2026-85091","CVE-2026-86145","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-89156","CVE-2026-89157","CVE-2026-89158","CVE-2026-89160","CVE-2026-89161","CVE-2026-89162","CVE-2026-8932","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"]
and ([.statements[] | select(.status == "not_affected") | .vulnerability.name] | sort)
== ["CVE-2024-2236","CVE-2024-56433","CVE-2025-1352","CVE-2025-1376","CVE-2025-66382","CVE-2026-13757","CVE-2026-18374","CVE-2026-18477","CVE-2026-18508","CVE-2026-27456","CVE-2026-3184","CVE-2026-40228","CVE-2026-53910","CVE-2026-54371","CVE-2026-56392","CVE-2026-56855","CVE-2026-57062","CVE-2026-78662","GO-2026-5932"]
and ([.statements[] | select(.status == "under_investigation") | .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.549 >/dev/null
+ "$vendor_pending_fixture" v1.6.550 >/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.549 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.550 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-source-gates.sh b/scripts/check-server-source-gates.sh
index 37adb9dcd2..33bf69106f 100755
--- a/scripts/check-server-source-gates.sh
+++ b/scripts/check-server-source-gates.sh
@@ -28,6 +28,8 @@ run_gate catalog_card_brand scripts/check-server-catalog-card-brand-patch.sh
run_gate dockerfile_artifact_downloads scripts/check-server-dockerfile-artifact-downloads.sh
run_gate production_build_context python3 scripts/test-server-build-context.py
run_gate offline_ubuntu_security_updates python3 scripts/test-offline-ubuntu-security-updates.py
+run_gate pcre2_official_backports python3 server/security/ubuntu-backports/test_pcre2_backport.py
+run_gate pcre2_runtime_verifier python3 scripts/test-pcre2-runtime-verifier.py
run_gate engine_archive_contract python3 scripts/test-engine-archive-contract.py
run_gate runtime_contract_readback python3 scripts/test-runtime-contract-readback.py
run_gate server_dev_dockerfile_downloads scripts/check-server-dev-dockerfile-downloads.sh
diff --git a/scripts/test-current-server-packaging-contract.py b/scripts/test-current-server-packaging-contract.py
index b348757060..92fe6c3293 100644
--- a/scripts/test-current-server-packaging-contract.py
+++ b/scripts/test-current-server-packaging-contract.py
@@ -42,7 +42,7 @@
ENGINE_SHA = 'f4889bfb8759a7664f07524bb129d0cda19f35604a06c877b6ca7033246df6fa'
ENGINE_SOURCE = 'dc5a8d7bc44131f609f72a6504483b692d47ce16'
OLD_COORDINATES = {
- 'v1.6.549': 'v1.6.521',
+ 'v1.6.550': '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.549'),
+ 'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.550'),
'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.549}'),
- 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.549}'),
+ 'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.550}'),
+ 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}'),
'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'),
@@ -600,15 +600,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.549"' not in gate:
+ if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"' not in gate:
raise AssertionError('CURRENT_VEX_GATE_PIN_MISMATCH')
- if '"$vendor_pending_fixture" v1.6.549 >/dev/null' not in gate:
+ if '"$vendor_pending_fixture" v1.6.550 >/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.549':
+ if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.550':
raise AssertionError('CURRENT_VEX_RELEASE_MISMATCH')
- if json.loads(files[VENDOR]).get('release') != 'v1.6.549':
+ if json.loads(files[VENDOR]).get('release') != 'v1.6.550':
raise AssertionError('CURRENT_VENDOR_RELEASE_MISMATCH')
- if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.549 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.550 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 +623,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.549"':
+ 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"':
'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.521"',
- '"$vendor_pending_fixture" v1.6.549 >/dev/null':
+ '"$vendor_pending_fixture" v1.6.550 >/dev/null':
'"$vendor_pending_fixture" v1.6.521 >/dev/null',
- 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.549 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.550 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():
@@ -1311,7 +1311,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.549}', candidate)
+ self.assertIn('export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.550}', 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 +1619,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.549', common)
+ self.assertNotIn('v1.6.550', 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 +1628,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.549', published)
+ self.assertNotIn('v1.6.550', published)
if __name__ == '__main__':
diff --git a/scripts/test-pcre2-runtime-verifier.py b/scripts/test-pcre2-runtime-verifier.py
new file mode 100644
index 0000000000..7908215196
--- /dev/null
+++ b/scripts/test-pcre2-runtime-verifier.py
@@ -0,0 +1,64 @@
+"""Source guards for the final-image verifier; native execution belongs to QA."""
+from pathlib import Path
+import re
+import unittest
+
+ROOT = Path(__file__).resolve().parents[1]
+SCRIPT = ROOT / 'server/artifacts/verify-pcre2-runtime.sh'
+
+
+class Pcre2RuntimeVerifier(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.text = SCRIPT.read_text(encoding='utf-8')
+
+ def test_exact_first_party_versions_and_dpkg_integrity(self):
+ self.assertIn('expected_version=10.46-1build1+pasturestack.1', self.text)
+ self.assertIn('for package in libpcre2-8-0 libpcre2-posix3;', self.text)
+ self.assertIn("${db:Status-Status}\\t${Version}\\n", self.text)
+ self.assertIn('dpkg --verify libpcre2-8-0 libpcre2-posix3', self.text)
+ self.assertRegex(self.text, r'if test -s "\$temporary/dpkg-verify"; then\s+'
+ r'cat "\$temporary/dpkg-verify" >&2\s+exit 1\s+fi')
+ # The installer must retain package files; this verifier never ignores
+ # missing documentation or filters a real ELF discrepancy.
+ self.assertNotIn('README.Debian', self.text)
+
+ def test_manifest_is_exact_and_binds_resolved_runtime_files(self):
+ for required in ['manifest=/usr/share/pasturestack/security/pcre2-runtime.sha256',
+ 'NF != 2', 'length($1) != 64', '$1 ~ /[^0-9a-f]/', 'NR != 2',
+ 'test "$count_pcre2:$count_posix" = 1:1',
+ 'sha256sum --check "$manifest"',
+ 'resolved=$(readlink -f -- "$file")']:
+ self.assertIn(required, self.text)
+
+ def test_all_five_consumers_resolve_the_verified_pcre2(self):
+ match = re.search(r'for consumer in (.*?);\s*do\b', self.text, re.S)
+ self.assertIsNotNone(match)
+ consumers = match[1].replace('\\\n', ' ').split()
+ self.assertEqual(['/usr/bin/grep', '/usr/bin/git', '/usr/sbin/mariadbd',
+ '/usr/lib/x86_64-linux-gnu/libselinux.so.1',
+ '/usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3'], consumers)
+ self.assertIn('ldd -r "$consumer"', self.text)
+ self.assertIn('not found|undefined symbol', self.text)
+ self.assertIn('if (count != 1) exit 1', self.text)
+ self.assertIn('test "$(readlink -f -- "$linked")" = "$pcre2"', self.text)
+
+ def test_minimal_real_grep_git_controls_cover_both_locales_and_interpreter(self):
+ for required in ['for test_locale in C C.utf8;', "for prefix in '' '(*NO_JIT)';",
+ '${prefix}^ASCII$', '${prefix}(*UTF)^漢字$', '${prefix}^CRLF\\r$',
+ "/usr/bin/git -C /usr/lib grep --no-index -P '^ID=ubuntu$' -- os-release",
+ 'cmp "$temporary/os-release" "$temporary/actual"']:
+ self.assertIn(required, self.text)
+ self.assertIn("printf 'ASCII\\n漢字\\nCRLF\\r\\n'", self.text)
+
+ def test_readback_never_installs_tools_or_opens_database(self):
+ self.assertNotRegex(self.text, r'(?m)^\s*(?:apt(?:-get)?|pip[0-9]*|python[0-9]*|readelf|nm)\b')
+ self.assertNotIn('dpkg --install', self.text)
+ self.assertNotRegex(self.text, r'(?m)^\s*/usr/sbin/mariadbd\b')
+ self.assertIn('mktemp -d /tmp/pcre2-runtime.XXXXXXXX', self.text)
+ self.assertIn('set -eu', self.text)
+ self.assertTrue(self.text.rstrip().endswith('echo PCRE2_RUNTIME_BACKPORT_OK'))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/server/Dockerfile.web-compose-release b/server/Dockerfile.web-compose-release
index 3cab787143..1b191db590 100644
--- a/server/Dockerfile.web-compose-release
+++ b/server/Dockerfile.web-compose-release
@@ -536,6 +536,7 @@ RUN set -eux; \
FROM ${UBUNTU_SECURITY_IMAGE} AS runtime_security_packages
ARG UBUNTU_RUNTIME_SECURITY_SNAPSHOT=20261002T000000Z
+ARG PCRE2_PACKAGE_VERSION=10.46-1build1+pasturestack.1
ARG CURL_PACKAGE_VERSION=8.18.0-1ubuntu2.8
ARG OPENSSL_PACKAGE_VERSION=3.5.5-1ubuntu3.7
ARG LIBDBI_PERL_PACKAGE_VERSION=1.647-1ubuntu0.26.04.4
@@ -546,6 +547,7 @@ 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
@@ -654,6 +656,24 @@ 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; \
+ for package in libpcre2-8-0 libpcre2-posix3; do \
+ archive="/out/pcre2/${package}_${PCRE2_PACKAGE_VERSION}_amd64.deb"; \
+ test "$(dpkg-deb -f "${archive}" Package)" = "${package}"; \
+ test "$(dpkg-deb -f "${archive}" Version)" = "${PCRE2_PACKAGE_VERSION}"; \
+ 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; \
+ cd /out; \
+ sha256sum pcre2/*.deb pcre2/provenance.json pcre2/pcre2.lock.json pcre2-runtime.sha256 >> SHA256SUMS
+
FROM ${BASE_IMAGE}
RUN rm -rf /opt/java/openjdk
@@ -671,7 +691,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.549
+ARG SERVER_RELEASE_TAG=v1.6.550
ARG API_EXPLORER_RELEASE_TAG=v1.1.20
ARG API_EXPLORER_ARTIFACT_SHA256=c989ee12a518f6d9d2b23b1f17f48d781aacf6ec31fb65291dd7de9b1661badb
ARG API_EXPLORER_COMMIT=a586f0dc0aa50df579049a8d4bf26f04400fdf75
@@ -695,6 +715,7 @@ 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 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
ARG WEB_CONSOLE_ARTIFACT_SHA256=26cbe1f3c057722c6682c43316021687508b7d9dd58c28c32713512d75238bb3
ARG WEB_CONSOLE_COMMIT=f905bc12daae18d98a6ac65942625562fff2a424
@@ -747,20 +768,30 @@ ARG MACHINE_DRIVER_BINARY_SHA256=9d379214de116542cb0e2b1a09c17409fd59fd2b0109428
ARG PACKET_DRIVER_BINARY_SHA256=b3e5c71928fcebe1bb648f79ebbf38af9ad32d6f6dbb87587e1b92c6c331e100
COPY --from=runtime_security_packages /out/ /tmp/pasturestack-runtime-security/
-COPY scripts/install-ubuntu-security-updates.sh security/ubuntu-security-packages.lock /tmp/pasturestack-security-input/
+COPY scripts/install-ubuntu-security-updates.sh security/ubuntu-security-packages.lock artifacts/verify-pcre2-runtime.sh /tmp/pasturestack-security-input/
# 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; \
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; \
sha256sum -c SHA256SUMS; \
install -m 0755 tar /usr/bin/tar; \
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}"; \
diff --git a/server/artifacts/verify-pcre2-runtime.sh b/server/artifacts/verify-pcre2-runtime.sh
new file mode 100644
index 0000000000..e99209dd00
--- /dev/null
+++ b/server/artifacts/verify-pcre2-runtime.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Read-only candidate-container verification; fixture files stay in /tmp.
+set -eu
+test "$#" -eq 0
+export LC_ALL=C
+cd /
+expected_version=10.46-1build1+pasturestack.1
+manifest=/usr/share/pasturestack/security/pcre2-runtime.sha256
+temporary=$(mktemp -d /tmp/pcre2-runtime.XXXXXXXX)
+trap 'rm -f -- "$temporary/"*; rmdir -- "$temporary"' EXIT HUP INT TERM
+
+for package in libpcre2-8-0 libpcre2-posix3; do
+ actual=$(dpkg-query --show --showformat='${db:Status-Status}\t${Version}\n' "$package")
+ test "$actual" = "$(printf 'installed\t%s' "$expected_version")"
+done
+if ! dpkg --verify libpcre2-8-0 libpcre2-posix3 > "$temporary/dpkg-verify" 2>&1; then
+ cat "$temporary/dpkg-verify" >&2
+ exit 1
+fi
+if test -s "$temporary/dpkg-verify"; then
+ cat "$temporary/dpkg-verify" >&2
+ exit 1
+fi
+
+pcre2=$(readlink -f /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0)
+posix=$(readlink -f /usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3)
+test -f "$pcre2" && test -f "$posix" && test "$pcre2" != "$posix"
+test -s "$manifest"
+awk 'NF != 2 || length($1) != 64 || $1 ~ /[^0-9a-f]/ {exit 1}
+ END {if (NR != 2) exit 1}' "$manifest"
+count_pcre2=0 count_posix=0
+while read -r expected_sha file; do
+ resolved=$(readlink -f -- "$file")
+ case "$resolved" in
+ "$pcre2") count_pcre2=$((count_pcre2 + 1));;
+ "$posix") count_posix=$((count_posix + 1));;
+ *) echo 'Unexpected file in PCRE2 runtime hash manifest' >&2; exit 1;;
+ esac
+done < "$manifest"
+test "$count_pcre2:$count_posix" = 1:1
+sha256sum --check "$manifest"
+
+for consumer in /usr/bin/grep /usr/bin/git /usr/sbin/mariadbd \
+ /usr/lib/x86_64-linux-gnu/libselinux.so.1 \
+ /usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3; do
+ test -f "$consumer"
+ if ! ldd -r "$consumer" > "$temporary/ldd" 2>&1; then
+ cat "$temporary/ldd" >&2
+ exit 1
+ fi
+ if grep -Eiq 'not found|undefined symbol' "$temporary/ldd"; then
+ cat "$temporary/ldd" >&2
+ exit 1
+ fi
+ linked=$(awk '$1 == "libpcre2-8.so.0" && $2 == "=>" {print $3; count++}
+ END {if (count != 1) exit 1}' "$temporary/ldd")
+ test "$(readlink -f -- "$linked")" = "$pcre2"
+ printf 'PCRE2_CONSUMER_LINK_OK consumer=%s resolved=%s\n' "$consumer" "$pcre2"
+done
+
+locale -a > "$temporary/locales"
+grep -Fx C "$temporary/locales" > /dev/null
+grep -Fx C.utf8 "$temporary/locales" > /dev/null
+printf 'ASCII\n漢字\nCRLF\r\n' > "$temporary/input"
+printf 'ASCII\n' > "$temporary/ascii"
+printf '漢字\n' > "$temporary/han"
+printf 'CRLF\r\n' > "$temporary/crlf"
+printf 'os-release:ID=ubuntu\n' > "$temporary/os-release"
+for test_locale in C C.utf8; do
+ for prefix in '' '(*NO_JIT)'; do
+ LC_ALL="$test_locale" /usr/bin/grep -P -- "${prefix}^ASCII$" "$temporary/input" > "$temporary/actual"
+ cmp "$temporary/ascii" "$temporary/actual"
+ LC_ALL="$test_locale" /usr/bin/grep -P -- "${prefix}(*UTF)^漢字$" "$temporary/input" > "$temporary/actual"
+ cmp "$temporary/han" "$temporary/actual"
+ LC_ALL="$test_locale" /usr/bin/grep -P -- "${prefix}^CRLF\r$" "$temporary/input" > "$temporary/actual"
+ cmp "$temporary/crlf" "$temporary/actual"
+ done
+ LC_ALL="$test_locale" GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null \
+ /usr/bin/git -C /usr/lib grep --no-index -P '^ID=ubuntu$' -- os-release > "$temporary/actual"
+ cmp "$temporary/os-release" "$temporary/actual"
+done
+echo PCRE2_RUNTIME_BACKPORT_OK
diff --git a/server/build-api-explorer-patch-image.sh b/server/build-api-explorer-patch-image.sh
index 6c2c44ab66..f9608ee2b4 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.549}
+server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}
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.549}
+image=${IMAGE:-pasturestack-validation/server:v1.6.550}
build_options=()
[[ "$revision" =~ ^[0-9a-f]{40}$ ]]
@@ -1025,6 +1025,7 @@ EOF
test "$(dpkg-query -W -f='"'"'${Version}'"'"' libpng16-16t64)" = 1.6.57-1ubuntu0.1
test "$(wc -l < /usr/share/pasturestack/security/libpng-runtime.sha256)" -eq 1
sha256sum -c /usr/share/pasturestack/security/libpng-runtime.sha256
+ sh /usr/local/libexec/pasturestack/verify-pcre2-runtime.sh
libpng_linkage=$(ldd -r /usr/lib/x86_64-linux-gnu/libpng16.so.16 2>&1)
require_resolved_linkage "$libpng_linkage"
test "$(readlink -f /usr/lib/x86_64-linux-gnu/libfreetype.so.6)" = /usr/lib/x86_64-linux-gnu/libfreetype.so.6.20.5
diff --git a/server/build-component-candidate.sh b/server/build-component-candidate.sh
index 717710eaa2..76ee284545 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.549}
+export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.550}
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 667d89eee2..aad6b9272d 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.549",
+ "@id": "https://github.com/PastureStack/server/security/openvex/v1.6.550",
"author": "PastureStack Security",
- "timestamp": "2026-10-23T19:28:00Z",
- "version": 62,
+ "timestamp": "2026-10-25T00:00:00Z",
+ "version": 63,
"statements": [
{
"vulnerability": {
@@ -827,6 +827,126 @@
"status": "not_affected",
"justification": "vulnerable_code_not_present",
"impact_statement": "The official libexpat1 2.7.4-1ubuntu0.2 package update retains the base image runtime surface: git-http-push, libexpat.so.1 and libexpat.so.1.11.2 are absent. Post-build validation checks both files, the linker cache and native executable dependencies. This is component absence, not an upstream fix; Canonical still defers CVE-2025-66382 for Ubuntu 26.04."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-103111"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-103111 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-86145"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-86145 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89156"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89156 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89157"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89157 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89158"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89158 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89160"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89160 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89161"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89161 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
+ },
+ {
+ "vulnerability": {
+ "name": "CVE-2026-89162"
+ },
+ "products": [
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-8-0@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ },
+ {
+ "@id": "pkg:deb/ubuntu/libpcre2-posix3@10.46-1build1%2Bpasturestack.1?arch=amd64&distro=ubuntu-26.04"
+ }
+ ],
+ "status": "fixed",
+ "impact_statement": "CVE-2026-89162 is fixed by the official PCRE2 release/pcre2-10.46 backports through 947809f11e9c9744aa0974b4b630a1ae461733dd (https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md). This first-party 10.46-1build1+pasturestack.1 rebuild retains original Ubuntu source packaging and ABI; it is not issued by Canonical. All twelve exact SHA-256-pinned official backports, the original three native test suites, package metadata, ELF integrity and five runtime consumers are mandatory build/publication gates. Raw scanner findings remain available; this disposition applies only to the two exact patched package PURLs."
}
]
}
diff --git a/server/security/ubuntu-backports/build-pcre2-backport.py b/server/security/ubuntu-backports/build-pcre2-backport.py
new file mode 100644
index 0000000000..0ab0d046f6
--- /dev/null
+++ b/server/security/ubuntu-backports/build-pcre2-backport.py
@@ -0,0 +1,384 @@
+#!/usr/bin/env python3
+"""Build first-party PCRE2 .debs with Ubuntu packaging and pinned upstream fixes.
+
+Run in a disposable Ubuntu 26.04 amd64 builder with the original Build-Depends,
+build-essential, fakeroot, patch, binutils, Python 3 and trusted CA certificates.
+This helper never installs packages. Only the two selected runtime .debs leave
+the build directory; the unchanged Debian rules run all upstream tests first.
+"""
+import argparse
+import datetime
+import email.utils
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import urllib.request
+
+HERE = Path(__file__).resolve().parent
+LOCK_FILE = HERE / 'pcre2.lock.json'
+PACKAGES = ['libpcre2-8-0', 'libpcre2-posix3']
+SOURCE_FILES = {'pcre2_10.46-1build1.dsc', 'pcre2_10.46.orig.tar.gz',
+ 'pcre2_10.46-1build1.diff.gz'}
+TESTS = ['pcre2_jit_test', 'RunTest', 'RunGrepTest']
+CVES = ['CVE-2026-103111', 'CVE-2026-86145', 'CVE-2026-89156', 'CVE-2026-89157',
+ 'CVE-2026-89158', 'CVE-2026-89160', 'CVE-2026-89161', 'CVE-2026-89162']
+
+
+def require(condition, message):
+ if not condition:
+ raise ValueError(message)
+
+
+def digest(file):
+ return hashlib.sha256(file.read_bytes()).hexdigest()
+
+
+def job_option(jobs):
+ # Select a literal argv token; never synthesize commands from CLI input.
+ if jobs == 2:
+ return '-j2'
+ if jobs == 4:
+ return '-j4'
+ raise ValueError('Build jobs must be 2 or 4')
+
+
+def load_lock(file):
+ lock = json.loads(file.read_text(encoding='utf-8'))
+ expected = {'schemaVersion': 1, 'sourcePackage': 'pcre2',
+ 'ubuntuSourceVersion': '10.46-1build1',
+ 'customVersion': '10.46-1build1+pasturestack.1',
+ 'distribution': 'resolute', 'osVersion': '26.04',
+ 'architecture': 'amd64', 'runtimePackages': PACKAGES, 'fixedCves': CVES}
+ require(all(lock.get(key) == value for key, value in expected.items()),
+ 'Unsupported PCRE2 lock identity')
+ require(type(lock.get('sourceDateEpoch')) is int and lock['sourceDateEpoch'] > 0,
+ 'SOURCE_DATE_EPOCH must be a positive integer')
+ sources, patches = lock['sourceFiles'], lock['patches']
+ require(len(sources) == 3 and {item['name'] for item in sources} == SOURCE_FILES,
+ 'Exactly the Ubuntu .dsc, original tarball and Debian diff are required')
+ require(len(patches) == 12 and len({item['name'] for item in patches}) == 12
+ and len({item['commit'] for item in patches}) == 12,
+ 'Exactly twelve distinct approved backports are required')
+ upstream = lock['upstream']
+ require(upstream['baseTag'] == 'pcre2-10.46'
+ and upstream['baseCommit'] == 'b2bd4254b379b9d7dc9a3dda060a7e27009ccdff'
+ and upstream['backportBranch'] == 'release/pcre2-10.46'
+ and upstream['backportCommit'] == '947809f11e9c9744aa0974b4b630a1ae461733dd'
+ and patches[-1]['commit'] == upstream['backportCommit'],
+ 'Unexpected upstream release/backport boundary')
+ for item in sources + patches:
+ require(Path(item['name']).name == item['name'] and '/' not in item['name']
+ and '\\' not in item['name'], 'Unsafe input filename')
+ require(re.fullmatch('[0-9a-f]{64}', item['sha256']) is not None,
+ 'Every source and patch needs a SHA-256 pin')
+ require(type(item['size']) is int and 0 < item['size'] < 8 * 1024 * 1024,
+ 'Invalid input size')
+ if item in sources:
+ expected_url = 'https://archive.ubuntu.com/ubuntu/pool/main/p/pcre2/' + item['name']
+ else:
+ require(re.fullmatch('[0-9a-f]{40}', item['commit']) is not None,
+ 'Every patch needs a complete commit pin')
+ expected_url = 'https://github.com/PCRE2Project/pcre2/commit/' + item['commit'] + '.patch'
+ require(item['url'] == expected_url, 'Unexpected source or patch URL')
+ require(upstream['lifecycleUrl'] == 'https://raw.githubusercontent.com/PCRE2Project/pcre2/'
+ 'pcre2-10.49/SUPPORT-LIFECYCLE.md'
+ and re.fullmatch('[0-9a-f]{64}', upstream['lifecycleSha256']) is not None,
+ 'The official supported-backport list must be pinned')
+ return lock
+
+
+class HttpsRedirect(urllib.request.HTTPRedirectHandler):
+ def redirect_request(self, request, file, code, message, headers, new_url):
+ require(new_url.startswith('https://'), 'Refusing HTTPS downgrade')
+ return super().redirect_request(request, file, code, message, headers, new_url)
+
+
+def verify_input(item, file):
+ require(file.is_file() and digest(file) == item['sha256'],
+ 'SHA-256 mismatch: ' + item['name'])
+ if 'size' in item:
+ require(file.stat().st_size == item['size'], 'Size mismatch: ' + item['name'])
+
+
+def download(item, directory, cache):
+ destination = directory / item['name']
+ cached = cache / item['name'] if cache else None
+ if cached and cached.exists():
+ # A poisoned cache fails closed instead of silently fetching other bytes.
+ verify_input(item, cached)
+ shutil.copyfile(cached, destination)
+ else:
+ opener = urllib.request.build_opener(HttpsRedirect())
+ with opener.open(item['url'], timeout=60) as response, destination.open('xb') as output:
+ require(response.url.startswith('https://'), 'Refusing non-HTTPS input')
+ size = 0
+ while True:
+ chunk = response.read(65536)
+ if not chunk:
+ break
+ size += len(chunk)
+ require(size < 8 * 1024 * 1024, 'Input exceeds download limit')
+ output.write(chunk)
+ verify_input(item, destination)
+ if cached:
+ shutil.copyfile(destination, cached)
+ verify_input(item, destination)
+ return destination
+
+
+def verify_source_control(lock, directory):
+ text = (directory / 'pcre2_10.46-1build1.dsc').read_text(encoding='utf-8')
+ require(re.search(r'^Source: pcre2$', text, re.M)
+ and re.search(r'^Version: 10\.46-1build1$', text, re.M)
+ and re.search(r'^Format: 1\.0$', text, re.M), 'Unexpected Ubuntu source control')
+ block = re.search(r'^Checksums-Sha256:\n((?: .+\n)+)', text, re.M)
+ require(block is not None, 'Source control has no SHA-256 manifest')
+ actual = {name: (sha, int(size)) for sha, size, name in
+ re.findall(r'^ ([0-9a-f]{64}) (\d+) (\S+)$', block[1], re.M)}
+ expected = {item['name']: (item['sha256'], item['size']) for item in lock['sourceFiles']
+ if not item['name'].endswith('.dsc')}
+ require(actual == expected, '.dsc source manifest does not match the lock')
+
+
+def verify_supported_patches(lock, lifecycle):
+ section = re.search(r'(?s)### PCRE2 10\.46 .*?(?=### PCRE2)', lifecycle)
+ require(section is not None, 'Official lifecycle has no PCRE2 10.46 section')
+ names = re.findall(r'`patches/([^`]+)`', section[0])
+ require(names == [item['name'] for item in lock['patches']],
+ 'Patch set/order differs from the official supported 10.46 backports')
+
+
+def verify_patch(item, file):
+ text = file.read_text(encoding='utf-8')
+ require(text.startswith('From ' + item['commit'] + ' '), 'Patch commit identity mismatch')
+ paths = re.findall(r'^diff --git a/(\S+) b/(\S+)$', text, re.M)
+ require(paths and all(left == right and left.startswith(('src/', 'testdata/'))
+ and '..' not in Path(left).parts for left, right in paths),
+ 'Approved patches may only change upstream source and regression tests')
+
+
+def build_environment(lock, jobs):
+ # Inherited DEB_BUILD_OPTIONS, compiler overrides or build profiles must not
+ # suppress tests, hardening, widths or JIT. No runtime APT is performed here.
+ return {'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
+ 'HOME': os.environ.get('HOME', '/tmp'), 'LANG': 'C.UTF-8', 'LC_ALL': 'C.UTF-8',
+ 'TZ': 'UTC', 'SOURCE_DATE_EPOCH': str(lock['sourceDateEpoch']),
+ 'DEB_BUILD_OPTIONS': 'parallel=' + str(jobs),
+ 'DEB_BUILD_MAINT_OPTIONS': 'hardening=+all', 'DEB_BUILD_PROFILES': '',
+ 'DPKG_COLORS': 'never'}
+
+
+def run(command, directory, environment, log):
+ with log.open('ab') as stream:
+ stream.write(('\n$ ' + json.dumps(command) + '\n').encode())
+ stream.flush()
+ process = subprocess.Popen(command, cwd=directory, env=environment,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ for line in process.stdout:
+ stream.write(line)
+ stream.flush()
+ sys.stdout.buffer.write(line)
+ sys.stdout.buffer.flush()
+ require(process.wait() == 0, 'Command failed; see ' + str(log))
+
+
+def capture(command, directory, environment):
+ return subprocess.check_output(command, cwd=directory, env=environment,
+ text=True, encoding='utf-8').strip()
+
+
+def packaging_snapshot(source):
+ return {file.relative_to(source).as_posix(): digest(file)
+ for file in sorted((source / 'debian').rglob('*'))
+ if file.is_file() and file.name != 'changelog'}
+
+
+def test_receipt(source):
+ results = []
+ for name in TESTS:
+ reports = list(source.rglob(name + '.trs'))
+ require(len(reports) == 1, 'Missing or ambiguous native test result: ' + name)
+ result = reports[0].read_text(encoding='utf-8')
+ require(re.search(r'^:test-result: PASS$', result, re.M) is not None,
+ 'Native test did not PASS: ' + name)
+ results.append({'test': name, 'status': 'PASS',
+ 'path': reports[0].relative_to(source).as_posix(),
+ 'sha256': digest(reports[0])})
+ return results
+
+
+def deb_field(file, field, directory, environment):
+ return capture(['dpkg-deb', '--field', str(file), field], directory, environment)
+
+
+def require_separate_paths(output, work, cache):
+ for directory in [work, cache]:
+ if directory:
+ require(output != directory and output not in directory.parents
+ and directory not in output.parents,
+ 'Output must be separate from build/cache directories')
+
+
+def verify_debs(lock, work, source, environment, log):
+ selected = []
+ extracted = work / 'verify-runtime'
+ extracted.mkdir()
+ for package, soname in zip(PACKAGES, ['libpcre2-8.so.0', 'libpcre2-posix.so.3']):
+ matches = list(work.glob(package + '_' + lock['customVersion'] + '_amd64.deb'))
+ require(len(matches) == 1, 'Missing or ambiguous runtime .deb: ' + package)
+ deb = matches[0]
+ for field, expected in [('Package', package), ('Version', lock['customVersion']),
+ ('Architecture', 'amd64')]:
+ require(deb_field(deb, field, work, environment) == expected,
+ 'Unexpected .deb metadata: ' + field)
+ source_identity = deb_field(deb, 'Source', work, environment)
+ require(source_identity in ('pcre2', 'pcre2 (' + lock['customVersion'] + ')'),
+ 'Unexpected .deb Source identity')
+ run(['dpkg-deb', '--extract', str(deb), str(extracted)], work, environment, log)
+ library = extracted / 'usr/lib/x86_64-linux-gnu' / soname
+ require(library.is_file(), 'Expected runtime SONAME is absent: ' + soname)
+ dynamic = capture(['readelf', '--dynamic', str(library)], work, environment)
+ headers = capture(['readelf', '--program-headers', str(library)], work, environment)
+ require('[' + soname + ']' in dynamic, 'Runtime SONAME changed')
+ require('BIND_NOW' in dynamic or re.search(r'FLAGS_1.*NOW', dynamic),
+ 'Runtime library lacks immediate binding hardening')
+ require('GNU_RELRO' in headers, 'Runtime library lacks RELRO hardening')
+ copyright_file = extracted / 'usr/share/doc' / package / 'copyright'
+ require(copyright_file.is_file() and digest(copyright_file) == digest(source / 'debian/copyright'),
+ 'Original copyright must remain in each runtime package')
+ selected.append({'package': package, 'version': lock['customVersion'],
+ 'architecture': 'amd64', 'file': deb.name, 'sha256': digest(deb),
+ 'soname': soname, 'hardening': ['RELRO', 'BIND_NOW']})
+ symbols = capture(['nm', '--dynamic', '--defined-only',
+ str(extracted / 'usr/lib/x86_64-linux-gnu/libpcre2-posix.so.3')], work, environment)
+ require(all(re.search(r'\bpcre2_' + name + r'(?:@@?\S+)?$', symbols, re.M)
+ for name in ['regcomp', 'regexec', 'regfree', 'regerror']),
+ 'Ubuntu POSIX wrapper ABI is missing')
+ return selected
+
+
+def build(args, lock):
+ os_release = dict(re.findall(r'^(\w+)=["\']?([^"\'\n]+)',
+ Path('/etc/os-release').read_text(encoding='utf-8'), re.M))
+ require(os_release.get('ID') == 'ubuntu' and os_release.get('VERSION_ID') == '26.04',
+ 'Use an isolated Ubuntu 26.04 builder')
+ environment = build_environment(lock, args.jobs)
+ for command in ['dpkg', 'dpkg-source', 'dpkg-buildpackage', 'dpkg-deb',
+ 'dpkg-buildflags', 'dpkg-checkbuilddeps', 'dpkg-query', 'patch', 'readelf', 'nm', 'fakeroot']:
+ require(shutil.which(command, path=environment['PATH']), 'Builder dependency missing: ' + command)
+ require(capture(['dpkg', '--print-architecture'], HERE, environment) == 'amd64',
+ 'Use an amd64 builder')
+ output = args.output_dir.resolve()
+ require(not output.exists(), 'Output directory must be new')
+ cache = args.cache_dir.resolve() if args.cache_dir else None
+ require_separate_paths(output, args.work_dir.resolve() if args.work_dir else None, cache)
+ if args.work_dir:
+ work = args.work_dir.resolve()
+ work.mkdir(parents=True, exist_ok=False)
+ else:
+ work = Path(tempfile.mkdtemp(prefix='pcre2-ubuntu-backport-'))
+ print('PCRE2_BACKPORT_WORK_DIR=' + str(work), flush=True)
+ inputs = work / 'inputs'
+ inputs.mkdir()
+ if cache:
+ cache.mkdir(parents=True, exist_ok=True)
+ for item in lock['sourceFiles'] + lock['patches']:
+ download(item, inputs, cache)
+ lifecycle = {'name': 'pcre2-10.49-SUPPORT-LIFECYCLE.md',
+ 'url': lock['upstream']['lifecycleUrl'],
+ 'sha256': lock['upstream']['lifecycleSha256']}
+ lifecycle_file = download(lifecycle, inputs, cache)
+ verify_supported_patches(lock, lifecycle_file.read_text(encoding='utf-8'))
+ verify_source_control(lock, inputs)
+ log = work / 'build.log'
+ source = work / 'pcre2-source'
+ run(['dpkg-source', '--extract', str(inputs / 'pcre2_10.46-1build1.dsc'), str(source)],
+ work, environment, log)
+ packaging = packaging_snapshot(source)
+ for item in lock['patches']:
+ patch = inputs / item['name']
+ verify_input(item, patch)
+ verify_patch(item, patch)
+ run(['patch', '-p1', '--fuzz=0', '--batch', '--forward', '--input', str(patch)],
+ source, environment, log)
+ require(packaging_snapshot(source) == packaging, 'Upstream patches changed Debian packaging')
+ changelog = source / 'debian/changelog'
+ date = email.utils.format_datetime(datetime.datetime.fromtimestamp(
+ lock['sourceDateEpoch'], datetime.timezone.utc))
+ entry = ('pcre2 (' + lock['customVersion'] + ') resolute; urgency=high\n\n'
+ ' * First-party PastureStack rebuild; not issued by Canonical.\n'
+ ' * Apply all twelve approved upstream 10.46 backports through\n'
+ ' 947809f11e9c9744aa0974b4b630a1ae461733dd, with regression tests.\n\n'
+ ' -- PastureStack maintainers ' + date + '\n\n')
+ changelog.write_text(entry + changelog.read_text(encoding='utf-8'), encoding='utf-8')
+ run(['dpkg-checkbuilddeps'], source, environment, log)
+ run(['dpkg-buildpackage', '-b', '-us', '-uc', job_option(args.jobs)], source, environment, log)
+ require({name: digest(source / name) for name in packaging} == packaging,
+ 'Build changed Debian packaging inputs')
+ tests = test_receipt(source)
+ selected = verify_debs(lock, work, source, environment, log)
+ output.mkdir(parents=True)
+ for item in selected:
+ shutil.copyfile(work / item['file'], output / item['file'])
+ shutil.copyfile(log, output / 'build.log')
+ shutil.copyfile(LOCK_FILE, output / 'pcre2.lock.json')
+ shutil.copyfile(lifecycle_file, output / lifecycle['name'])
+ shutil.copyfile(source / 'debian/copyright', output / 'COPYRIGHT')
+ for result in tests:
+ report = source / result['path']
+ shutil.copyfile(report, output / (result['test'] + '.trs'))
+ test_log = report.with_suffix('.log')
+ require(test_log.is_file(), 'Native test output is missing')
+ shutil.copyfile(test_log, output / (result['test'] + '.log'))
+ for pattern in ['pcre2_*.buildinfo', 'pcre2_*.changes']:
+ files = list(work.glob(pattern))
+ require(len(files) == 1, 'Native package build receipt is absent or ambiguous')
+ shutil.copyfile(files[0], output / files[0].name)
+ (output / 'builder-packages.tsv').write_text(capture(
+ ['dpkg-query', '--show', '--showformat=${binary:Package}\t${Version}\n'],
+ work, environment) + '\n', encoding='utf-8')
+ provenance = {'schemaVersion': 1, 'identity': lock['identity'], 'sourceLockSha256': digest(LOCK_FILE),
+ 'sourceDateEpoch': lock['sourceDateEpoch'], 'sourceFiles': lock['sourceFiles'],
+ 'upstream': lock['upstream'], 'patches': lock['patches'], 'fixedCves': lock['fixedCves'],
+ 'debianPackagingInputSha256': packaging, 'buildEnvironment': environment,
+ 'buildCommand': ['dpkg-buildpackage', '-b', '-us', '-uc', job_option(args.jobs)],
+ 'tests': tests, 'runtimePackages': selected,
+ 'scope': 'Ubuntu packaging built all configured widths/JIT; only two runtime .debs exported',
+ 'signaturePolicy': 'Source bytes are SHA-256 pinned from official Ubuntu HTTPS; no Canonical signing claim for custom outputs.'}
+ (output / 'provenance.json').write_text(json.dumps(provenance, indent=2) + '\n', encoding='utf-8')
+ (output / 'manifest.tsv').write_text('lock-sha256\t' + digest(LOCK_FILE) + '\n' + ''.join(
+ '\t'.join([item['package'], item['version'], item['architecture'], item['sha256'], item['file']]) + '\n'
+ for item in selected), encoding='utf-8')
+ (output / 'SHA256SUMS').write_text(''.join(digest(file) + ' ' + file.name + '\n'
+ for file in sorted(output.iterdir()) if file.is_file()), encoding='utf-8')
+ print('PCRE2_BACKPORT_BUILT packages=2 tests=3 lock=' + digest(LOCK_FILE), flush=True)
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--validate-lock', action='store_true')
+ parser.add_argument('--output-dir', type=Path)
+ parser.add_argument('--work-dir', type=Path)
+ parser.add_argument('--cache-dir', type=Path)
+ parser.add_argument('--jobs', type=int, choices=(2, 4), default=2)
+ args = parser.parse_args()
+ lock = load_lock(LOCK_FILE)
+ if args.validate_lock:
+ print('PCRE2_BACKPORT_LOCK_OK sha256=' + digest(LOCK_FILE))
+ return
+ require(args.output_dir is not None, '--output-dir is required for a native build')
+ build(args, lock)
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
+ print('PCRE2_BACKPORT_FAILED: ' + str(error), file=sys.stderr)
+ sys.exit(1)
diff --git a/server/security/ubuntu-backports/pcre2.lock.json b/server/security/ubuntu-backports/pcre2.lock.json
new file mode 100644
index 0000000000..dce2e6c169
--- /dev/null
+++ b/server/security/ubuntu-backports/pcre2.lock.json
@@ -0,0 +1,141 @@
+{
+ "schemaVersion": 1,
+ "sourcePackage": "pcre2",
+ "ubuntuSourceVersion": "10.46-1build1",
+ "customVersion": "10.46-1build1+pasturestack.1",
+ "distribution": "resolute",
+ "osVersion": "26.04",
+ "architecture": "amd64",
+ "sourceDateEpoch": 1792857600,
+ "sourcePublication": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/18107131",
+ "sourceFiles": [
+ {
+ "name": "pcre2_10.46-1build1.dsc",
+ "url": "https://archive.ubuntu.com/ubuntu/pool/main/p/pcre2/pcre2_10.46-1build1.dsc",
+ "sha256": "0e10c366d10e72b6940fcf37b61c2309d0136e05edff7adb1843030c0765f155",
+ "size": 2221
+ },
+ {
+ "name": "pcre2_10.46.orig.tar.gz",
+ "url": "https://archive.ubuntu.com/ubuntu/pool/main/p/pcre2/pcre2_10.46.orig.tar.gz",
+ "sha256": "8d28d7f2c3b970c3a4bf3776bcbb5adfc923183ce74bc8df1ebaad8c1985bd07",
+ "size": 2718545
+ },
+ {
+ "name": "pcre2_10.46-1build1.diff.gz",
+ "url": "https://archive.ubuntu.com/ubuntu/pool/main/p/pcre2/pcre2_10.46-1build1.diff.gz",
+ "sha256": "3534c69dec410207e74bf9fd6ed334ad5827fb6de1d0715175b7a14257c5bc72",
+ "size": 8804
+ }
+ ],
+ "upstream": {
+ "repository": "https://github.com/PCRE2Project/pcre2",
+ "baseTag": "pcre2-10.46",
+ "baseCommit": "b2bd4254b379b9d7dc9a3dda060a7e27009ccdff",
+ "backportBranch": "release/pcre2-10.46",
+ "backportCommit": "947809f11e9c9744aa0974b4b630a1ae461733dd",
+ "lifecycleUrl": "https://raw.githubusercontent.com/PCRE2Project/pcre2/pcre2-10.49/SUPPORT-LIFECYCLE.md",
+ "lifecycleSha256": "64a3e10be90d7b3c218d05f21c53aa2511b9100e01f98c77c199c473b33f1856"
+ },
+ "patches": [
+ {
+ "name": "pcre2-10.47-Fix-for-callback.patch",
+ "commit": "16c1b6fb2a45c8bebcc46aa22e7c0b26759b77a0",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/16c1b6fb2a45c8bebcc46aa22e7c0b26759b77a0.patch",
+ "sha256": "a7893d9a54fccd89c9eb2da0aead0cfae11bd3e95565f490dd57eba75c03f408",
+ "size": 6067
+ },
+ {
+ "name": "pcre2-10.48-Write-serialization-padding.patch",
+ "commit": "efeabb2dc9adfb91abf8feecd8daccb2a38ea94f",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/efeabb2dc9adfb91abf8feecd8daccb2a38ea94f.patch",
+ "sha256": "ccb97e38f86bddc4944d091a4db18fe3dd8585421bd58fef92ab48b957a3facf",
+ "size": 3159
+ },
+ {
+ "name": "pcre2-10.48-Fix-character-list-generator.patch",
+ "commit": "a4318173dab0c332ab7d859f37ac71099326a0f2",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/a4318173dab0c332ab7d859f37ac71099326a0f2.patch",
+ "sha256": "605e1c8e0a1baf7542d4d4833afe6721c80ab0b6a199b92d10748f04c3338a92",
+ "size": 5462
+ },
+ {
+ "name": "pcre2-10.48-Fix-scan-prefix-repeat-reset.patch",
+ "commit": "f945e356e0ba203a974717af2cff0baee01f1db4",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/f945e356e0ba203a974717af2cff0baee01f1db4.patch",
+ "sha256": "e71e2b92a1cf76e42c08134055826911f7d5cd3398242dffee5b04cd57a49f78",
+ "size": 1436
+ },
+ {
+ "name": "pcre2-10.48-Fix-JIT-STR_END.patch",
+ "commit": "0aa5bbca3ac70e6c22562488ead6cb2032447fc5",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/0aa5bbca3ac70e6c22562488ead6cb2032447fc5.patch",
+ "sha256": "d835898636b4063101d1cb546e6e044f888cb82d1e2f564bdd8230fd767528fa",
+ "size": 1573
+ },
+ {
+ "name": "pcre2-10.48-Fix-JIT-match-context-reuse.patch",
+ "commit": "e5b9232fc4cd6a0df312ae25568e72cd337cafa2",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/e5b9232fc4cd6a0df312ae25568e72cd337cafa2.patch",
+ "sha256": "708a2cfe0a786411952a5de364a054f4a3ab1027aa9a8b1fc4a87d8adf780df7",
+ "size": 6423
+ },
+ {
+ "name": "pcre2-10.48-Check-JIT-mode-before-validation.patch",
+ "commit": "ab7097fbde778e0e4d17752ba2a14a7430d35385",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/ab7097fbde778e0e4d17752ba2a14a7430d35385.patch",
+ "sha256": "76e4fdd573ecc3ee51dc822c33808bb8a74ce77884f29fbfc181b634662d7f56",
+ "size": 9222
+ },
+ {
+ "name": "pcre2-10.48-Fix-allocation-byte-sizing.patch",
+ "commit": "31ec59526d641b85108c726fe201effc5dca8627",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/31ec59526d641b85108c726fe201effc5dca8627.patch",
+ "sha256": "437013bddd46cc860784b9261dbc94b024c6c4ea22d3f235aadd33f22e591167",
+ "size": 2799
+ },
+ {
+ "name": "pcre2-10.48-Fix-invalid-UTF-backwards-scans.patch",
+ "commit": "e1fd2e68fe364d14a01ec42bf07201d9820eee8e",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/e1fd2e68fe364d14a01ec42bf07201d9820eee8e.patch",
+ "sha256": "72d1621c28afa0265e7bb01f6876eea43eb5d968b3ce8fe5b4f3fe78c5e5a893",
+ "size": 6835
+ },
+ {
+ "name": "pcre2-10.48-Fix-compiler-integer-overflows.patch",
+ "commit": "983158a222ec27a93ce678778f9343f7dfa35845",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/983158a222ec27a93ce678778f9343f7dfa35845.patch",
+ "sha256": "1968d664b3a2f02d6256886d2049ba802c07cc797877765d1c2daac678c1363b",
+ "size": 6693
+ },
+ {
+ "name": "pcre2-10.48-Fix-DFA-workspace-overflows.patch",
+ "commit": "bcaf1ba40748a27e1e666491a0e9887f0f5f2965",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/bcaf1ba40748a27e1e666491a0e9887f0f5f2965.patch",
+ "sha256": "1e906da52268ecdd03459d1086922af24ac010f8672a26d6638fc22e395bd6bb",
+ "size": 5636
+ },
+ {
+ "name": "pcre2-10.49-Fix-large-stack-allocation.patch",
+ "commit": "947809f11e9c9744aa0974b4b630a1ae461733dd",
+ "url": "https://github.com/PCRE2Project/pcre2/commit/947809f11e9c9744aa0974b4b630a1ae461733dd.patch",
+ "sha256": "cdd5e0241e5dd3a7a3e3f773c10f20c8f353063fcc8f737f6a391503cb6bd037",
+ "size": 4535
+ }
+ ],
+ "runtimePackages": [
+ "libpcre2-8-0",
+ "libpcre2-posix3"
+ ],
+ "fixedCves": [
+ "CVE-2026-103111",
+ "CVE-2026-86145",
+ "CVE-2026-89156",
+ "CVE-2026-89157",
+ "CVE-2026-89158",
+ "CVE-2026-89160",
+ "CVE-2026-89161",
+ "CVE-2026-89162"
+ ],
+ "identity": "First-party PastureStack rebuild of official Ubuntu source with approved upstream backports; not a Canonical-issued package."
+}
diff --git a/server/security/ubuntu-backports/test_pcre2_backport.py b/server/security/ubuntu-backports/test_pcre2_backport.py
new file mode 100644
index 0000000000..c4f0dd21d1
--- /dev/null
+++ b/server/security/ubuntu-backports/test_pcre2_backport.py
@@ -0,0 +1,192 @@
+"""Focused, network-free checks for the pinned PCRE2 source-build boundary."""
+import copy
+import hashlib
+import importlib.util
+import json
+import os
+import subprocess
+from pathlib import Path
+import sys
+import tempfile
+import unittest
+from unittest import mock
+import urllib.request
+
+HERE = Path(__file__).resolve().parent
+sys.dont_write_bytecode = True
+SPEC = importlib.util.spec_from_file_location('pcre2_backport', HERE / 'build-pcre2-backport.py')
+MODULE = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(MODULE)
+
+
+class Pcre2Backport(unittest.TestCase):
+ def test_job_flags_are_literal_and_untrusted_lock_override_is_rejected(self):
+ self.assertEqual('-j2', MODULE.job_option(2))
+ self.assertEqual('-j4', MODULE.job_option(4))
+ for value in [0, 1, 32, '2', '-j2;false']:
+ with self.subTest(value=value), self.assertRaises(ValueError):
+ MODULE.job_option(value)
+ result = subprocess.run([sys.executable, str(HERE / 'build-pcre2-backport.py'),
+ '--validate-lock', '--lock', str(HERE / 'pcre2.lock.json')],
+ capture_output=True, text=True)
+ self.assertEqual(2, result.returncode)
+ self.assertIn('unrecognized arguments: --lock', result.stderr)
+
+ def setUp(self):
+ self.temporary = tempfile.TemporaryDirectory(prefix='pcre2-backport-contract-')
+ self.addCleanup(self.temporary.cleanup)
+ self.root = Path(self.temporary.name)
+ self.lock = MODULE.load_lock(HERE / 'pcre2.lock.json')
+
+ def load(self, value):
+ file = self.root / 'fixture.lock.json'
+ file.write_text(json.dumps(value), encoding='utf-8')
+ return MODULE.load_lock(file)
+
+ def test_locked_official_sources_and_complete_approved_boundary(self):
+ self.assertEqual('0e10c366d10e72b6940fcf37b61c2309d0136e05edff7adb1843030c0765f155',
+ self.lock['sourceFiles'][0]['sha256'])
+ self.assertEqual(12, len(self.lock['patches']))
+ self.assertEqual(8, len(self.lock['fixedCves']))
+ self.assertEqual(MODULE.PACKAGES, self.lock['runtimePackages'])
+ self.assertEqual('947809f11e9c9744aa0974b4b630a1ae461733dd',
+ self.lock['patches'][-1]['commit'])
+
+ def test_foreign_source_version_architecture_or_package_set_fails(self):
+ for field, bad in [('ubuntuSourceVersion', '10.49-1'), ('customVersion', '10.46-1build1'),
+ ('architecture', 'arm64'), ('runtimePackages', ['libpcre2-8-0']),
+ ('fixedCves', ['CVE-2026-103111']),
+ ('sourceDateEpoch', True)]:
+ with self.subTest(field=field):
+ altered = copy.deepcopy(self.lock)
+ altered[field] = bad
+ with self.assertRaises(ValueError): self.load(altered)
+
+ def test_missing_duplicate_or_unpinned_patch_is_rejected(self):
+ for mutation in ['missing', 'duplicate', 'hash', 'commit', 'path', 'url']:
+ with self.subTest(mutation=mutation):
+ altered = copy.deepcopy(self.lock)
+ if mutation == 'missing': altered['patches'].pop(0)
+ if mutation == 'duplicate': altered['patches'][1] = altered['patches'][0]
+ if mutation == 'hash': altered['patches'][0]['sha256'] = ''
+ if mutation == 'commit': altered['patches'][0]['commit'] = '16c1b6f'
+ if mutation == 'path': altered['patches'][0]['name'] = '../outside.patch'
+ if mutation == 'url': altered['patches'][0]['url'] = 'http://untrusted.invalid/patch'
+ with self.assertRaises(ValueError): self.load(altered)
+
+ def test_source_set_and_source_url_cannot_be_replaced(self):
+ for mutation in ['missing', 'url', 'duplicate']:
+ with self.subTest(mutation=mutation):
+ altered = copy.deepcopy(self.lock)
+ if mutation == 'missing': altered['sourceFiles'].pop()
+ if mutation == 'duplicate': altered['sourceFiles'][1] = altered['sourceFiles'][0]
+ if mutation == 'url': altered['sourceFiles'][0]['url'] = 'https://example.invalid/source.dsc'
+ with self.assertRaises(ValueError): self.load(altered)
+
+ def test_cached_bytes_are_verified_and_tamper_never_fetches(self):
+ cache, output = self.root / 'cache', self.root / 'input'
+ cache.mkdir(); output.mkdir()
+ data = b'official fixture bytes\n'
+ item = {'name': 'input.patch', 'url': 'https://example.invalid/never-requested',
+ 'sha256': hashlib.sha256(data).hexdigest(), 'size': len(data)}
+ cached = cache / item['name']; cached.write_bytes(data)
+ with mock.patch.object(urllib.request, 'build_opener') as network:
+ self.assertEqual(data, MODULE.download(item, output, cache).read_bytes())
+ cached.write_bytes(data + b'tampered')
+ with self.assertRaises(ValueError): MODULE.download(item, output, cache)
+ network.assert_not_called()
+
+ def test_https_redirect_cannot_downgrade(self):
+ request = urllib.request.Request('https://github.com/official/patch')
+ with self.assertRaises(ValueError):
+ MODULE.HttpsRedirect().redirect_request(request, None, 302, 'Found', {},
+ 'http://example.invalid/patch')
+
+ def source_control(self):
+ inputs = [item for item in self.lock['sourceFiles'] if not item['name'].endswith('.dsc')]
+ return ('Format: 1.0\nSource: pcre2\nVersion: 10.46-1build1\nChecksums-Sha256:\n' +
+ ''.join(' {sha256} {size} {name}\n'.format(**item) for item in inputs))
+
+ def test_dsc_binds_original_and_debian_source_checksums(self):
+ file = self.root / 'pcre2_10.46-1build1.dsc'
+ original = self.source_control()
+ file.write_text(original, encoding='utf-8')
+ MODULE.verify_source_control(self.lock, self.root)
+ for altered in [original.replace('10.46-1build1\n', '10.49-1\n'),
+ original.replace(self.lock['sourceFiles'][1]['sha256'], '0' * 64),
+ original.replace('Source: pcre2', 'Source: other'),
+ original.rsplit('\n', 2)[0] + '\n']:
+ with self.subTest(altered=altered):
+ file.write_text(altered, encoding='utf-8')
+ with self.assertRaises(ValueError): MODULE.verify_source_control(self.lock, self.root)
+
+ def test_lifecycle_set_and_order_must_match(self):
+ section = ('### PCRE2 10.46 release\nBackports to apply:\n' + ''.join(
+ '* `patches/' + item['name'] + '`\n' for item in self.lock['patches']) + '### PCRE2 10.47\n')
+ MODULE.verify_supported_patches(self.lock, section)
+ with self.assertRaises(ValueError):
+ MODULE.verify_supported_patches(self.lock, section.replace(
+ self.lock['patches'][0]['name'], 'unapproved.patch'))
+ reversed_lock = copy.deepcopy(self.lock)
+ reversed_lock['patches'].reverse()
+ with self.assertRaises(ValueError): MODULE.verify_supported_patches(reversed_lock, section)
+
+ def test_patch_identity_and_packaging_boundary(self):
+ item = self.lock['patches'][0]
+ file = self.root / 'input.patch'
+ original = ('From ' + item['commit'] + ' Mon Sep 17 00:00:00 2001\n'
+ 'diff --git a/src/pcre2_match.c b/src/pcre2_match.c\n')
+ file.write_text(original, encoding='utf-8')
+ MODULE.verify_patch(item, file)
+ for altered in [original.replace(item['commit'], '0' * 40),
+ original.replace('src/pcre2_match.c', 'debian/rules'),
+ original.replace('src/pcre2_match.c', 'src/../../outside')]:
+ file.write_text(altered, encoding='utf-8')
+ with self.assertRaises(ValueError): MODULE.verify_patch(item, file)
+
+ def test_inherited_environment_cannot_skip_tests_or_replace_flags(self):
+ with mock.patch.dict(os.environ, {'DEB_BUILD_OPTIONS': 'nocheck noopt',
+ 'DEB_BUILD_PROFILES': 'nocheck', 'CFLAGS': '-O0',
+ 'DEB_CFLAGS_MAINT_APPEND': '-Dunsafe=1'}):
+ environment = MODULE.build_environment(self.lock, 2)
+ self.assertEqual('parallel=2', environment['DEB_BUILD_OPTIONS'])
+ self.assertEqual('', environment['DEB_BUILD_PROFILES'])
+ self.assertEqual('hardening=+all', environment['DEB_BUILD_MAINT_OPTIONS'])
+ self.assertEqual(str(self.lock['sourceDateEpoch']), environment['SOURCE_DATE_EPOCH'])
+ self.assertNotIn('CFLAGS', environment)
+ self.assertNotIn('DEB_CFLAGS_MAINT_APPEND', environment)
+
+ def test_native_pass_receipt_requires_every_real_test_report(self):
+ for name in MODULE.TESTS:
+ (self.root / (name + '.trs')).write_text(':test-result: PASS\n', encoding='utf-8')
+ self.assertEqual(3, len(MODULE.test_receipt(self.root)))
+ file = self.root / 'RunTest.trs'
+ for result in ['FAIL', 'SKIP', 'XFAIL', 'ERROR']:
+ file.write_text(':test-result: ' + result + '\n', encoding='utf-8')
+ with self.assertRaises(ValueError): MODULE.test_receipt(self.root)
+ file.unlink()
+ with self.assertRaises(ValueError): MODULE.test_receipt(self.root)
+
+ def test_original_packaging_snapshot_excludes_only_changelog(self):
+ debian = self.root / 'debian'; debian.mkdir()
+ (debian / 'rules').write_bytes(b'original rules\n')
+ (debian / 'copyright').write_bytes(b'original copyright\n')
+ (debian / 'changelog').write_bytes(b'original changelog\n')
+ before = MODULE.packaging_snapshot(self.root)
+ (debian / 'changelog').write_bytes(b'first-party rebuild entry\n')
+ self.assertEqual(before, MODULE.packaging_snapshot(self.root))
+ (debian / 'rules').write_bytes(b'--disable-jit\n')
+ self.assertNotEqual(before, MODULE.packaging_snapshot(self.root))
+
+ def test_output_cannot_overlap_build_or_download_cache(self):
+ MODULE.require_separate_paths(self.root / 'out', self.root / 'build', self.root / 'cache')
+ for other in [self.root / 'out', self.root / 'out/nested', self.root]:
+ with self.subTest(other=other):
+ with self.assertRaises(ValueError):
+ MODULE.require_separate_paths(self.root / 'out', other, None)
+ with self.assertRaises(ValueError):
+ MODULE.require_separate_paths(self.root / 'out', None, other)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/server/security/vendor-pending.json b/server/security/vendor-pending.json
index 09fe071597..097ec72aca 100644
--- a/server/security/vendor-pending.json
+++ b/server/security/vendor-pending.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
- "release": "v1.6.549",
- "generatedAt": "2026-10-23T03:05:10Z",
+ "release": "v1.6.550",
+ "generatedAt": "2026-10-25T00:00:00Z",
"policy": {
"allowedSeverities": [
"LOW",
@@ -43,26 +43,6 @@
"verifiedAt": "2026-10-20T01:35:18Z",
"vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-102474.json"
},
- {
- "vulnerabilityId": "CVE-2026-103111",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Vulnerable",
- "source": "https://ubuntu.com/security/CVE-2026-103111",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-103111.json"
- },
{
"vulnerabilityId": "CVE-2026-107161",
"severity": "MEDIUM",
@@ -99,25 +79,6 @@
"verifiedAt": "2026-10-20T01:35:18Z",
"vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-67693.json"
},
- {
- "vulnerabilityId": "CVE-2026-86145",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "source": "https://ubuntu.com/security/CVE-2026-86145",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-86145.json"
- },
{
"vulnerabilityId": "CVE-2026-88647",
"severity": "MEDIUM",
@@ -173,125 +134,6 @@
"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-89156",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Vulnerable",
- "source": "https://ubuntu.com/security/CVE-2026-89156",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89156.json"
- },
- {
- "vulnerabilityId": "CVE-2026-89157",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Vulnerable",
- "source": "https://ubuntu.com/security/CVE-2026-89157",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89157.json"
- },
- {
- "vulnerabilityId": "CVE-2026-89158",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Vulnerable",
- "source": "https://ubuntu.com/security/CVE-2026-89158",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89158.json"
- },
- {
- "vulnerabilityId": "CVE-2026-89160",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Vulnerable",
- "source": "https://ubuntu.com/security/CVE-2026-89160",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89160.json"
- },
- {
- "vulnerabilityId": "CVE-2026-89161",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "source": "https://ubuntu.com/security/CVE-2026-89161",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89161.json"
- },
- {
- "vulnerabilityId": "CVE-2026-89162",
- "severity": "MEDIUM",
- "vendorStatus": "needs-evaluation",
- "upstreamVendorStatus": "Needs evaluation",
- "source": "https://ubuntu.com/security/CVE-2026-89162",
- "reviewAfter": "2026-10-27",
- "packages": [
- {
- "name": "libpcre2-8-0",
- "installedVersion": "10.46-1build1"
- },
- {
- "name": "libpcre2-posix3",
- "installedVersion": "10.46-1build1"
- }
- ],
- "verifiedAt": "2026-10-20T01:35:18Z",
- "vendorEvidence": "https://raw.githubusercontent.com/canonical/ubuntu-security-notices/main/osv/cve/2026/UBUNTU-CVE-2026-89162.json"
- },
{
"vulnerabilityId": "CVE-2026-95184",
"severity": "MEDIUM",