Skip to content

Commit 30cbdf7

Browse files
committed
release: bump to v0.9.12.3
- in-app updater + update.sh always re-download the full tree now (no version-skip early-exit) so partial/stale installs self-heal — 8a021c1 / 20fe5c1 / 5358500 - fix self-signed cert generation on fresh Docker containers (ccdb7b1): create the config/ssl dir before writing, so HTTPS login works on :latest instead of falling back to plain HTTP - add images/sponsors/datimo.png to version.json update_files so older updaters pull the new sponsor logo too
1 parent 76ec4f7 commit 30cbdf7

5 files changed

Lines changed: 9 additions & 8 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</p>
2222

2323
<p align="center">
24-
<img src="https://img.shields.io/badge/version-0.9.12.2--beta-blue" alt="Version"/>
24+
<img src="https://img.shields.io/badge/version-0.9.12.3--beta-blue" alt="Version"/>
2525
<img src="https://img.shields.io/badge/python-3.8+-green" alt="Python"/>
2626
<img src="https://img.shields.io/badge/license-AGPL--3.0--License-orange" alt="License"/>
2727
</p>

pegaprox/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from pathlib import Path
99

1010
# Version
11-
PEGAPROX_VERSION = "Beta 0.9.12.2"
12-
PEGAPROX_BUILD = "2026.06.05"
11+
PEGAPROX_VERSION = "Beta 0.9.12.3"
12+
PEGAPROX_BUILD = "2026.06.08"
1313

1414
# File Paths & Directories
1515
CONFIG_DIR = 'config'

version.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"version": "0.9.12.2",
3-
"build": "2026.06.05",
4-
"release_date": "2026-06-05",
2+
"version": "0.9.12.3",
3+
"build": "2026.06.08",
4+
"release_date": "2026-06-08",
55
"changelog": [
66
"security: v0.9.12.2 — authorized penetration test + remediation. All CRITICAL and HIGH findings fixed and each E2E-verified against a live lab. CRITICAL: (C-1) the VNC/termproxy console no longer hands the cluster-wide PVE session ticket (PVEAuthCookie = effectively root on pve:8006) to the browser — the standalone WS proxy now fetches it server-side via cluster-creds / ws-token-validate, the browser only ever sees the per-console scoped termproxy ticket; (C-2/M-2) V2P migration's esxi_datastore / esxi_vm_dir are validated against a strict single-name allowlist at the API boundary AND re-validated at the v2p chokepoint, plus shlex.quote on the sshfs mount paths — closes a root command-injection on the PVE node reachable by a default migrate-permission user. HIGH: (H-1/H-2) every console path (VNC route, in-process + standalone WS proxies, termproxy ticket) now enforces cluster access AND per-VM user_can_access_vm('vm.console') — previously only the vm.console permission was checked, so a console user could reach any VM on any cluster/tenant; (H-3) API-token perms are evaluated against the token's floored role, not the owner's — an admin-owned viewer/user token no longer inherits admin on perms-guarded routes; (H-4) web-push send re-validates the endpoint host server-side + refuses redirects; (H-5) the plain→SQLCipher migration's plaintext DB backup (every cluster password in clear) is now securely shredded on startup past a 1h window (PEGAPROX_PLAIN_BACKUP_RETENTION_S) instead of retained indefinitely next to the keystore; (H-6) request bodies are capped per-request so a chunked Transfer-Encoding body can no longer bypass the size limit and OOM the process. MEDIUM: (M-3) cluster-group endpoints read the correct tenant key (was user.get('tenant'), always None → cross-tenant group/metrics leak) and the manual cross-cluster balance + lb-history now enforce group ownership (was a BOLA that could trigger live VM migrations on another tenant's group); (M-7) alert webhook channels (Slack/Discord/Teams/ntfy/generic) now pass the SSRF guard before sending + refuse redirects, and the channel-test endpoint no longer leaks the upstream HTTP status (port-scan oracle) — internal targets opt-in via alert_webhook_allow_private; (M-8/M-9) ACME re-validates every directory-supplied follow-on URL and ACME/SIEM/site-recovery outbound calls no longer follow redirects to internal hosts. OIDC was deliberately left untouched. UI: Corporate Layout polish within its flat Clarity aesthetic — theme-aware metric/threshold colors (light mode now correct), keyboard focus ring on toolbar buttons, fade-in on action dropdowns, symmetric property-grid padding, flat (non-gradient) light-mode modal header. CI: Docker image builds only on a released version tag (not every main push); release-image apt-poll retries raised 120→150; the LXC template now also ships as pegaprox-latest-lxc-amd64.tar.xz so releases/latest/download/ always resolves to the newest build. Scaling/stability hardening from the 30-cluster/100-node audit carried over from the Testing line (off-hub metrics reads/writes, broadcast throttling, watched-cluster gating, bounded caches, syslog receiver DoS fix + UI toggle).",
77
"hotfix: v0.9.12.1 — fixes #514 (jostrasser) PBS-host placeholder allowlist regression. The Aikido SSRF-hardening patch (PR #475) that landed in v0.9.12.0 shipped `_validate_pbs_host` with `allowed_domains = ['example.com']` as a literal placeholder. Effect: at boot every customer-configured PBS was rejected with 'Invalid or disallowed host', and connect/test endpoints returned 400 'Invalid PBS host' for any non-example.com input. Allowlist removed in pegaprox/core/pbs.py — the format regex (`^[a-zA-Z0-9.\\-:]+$`) is the actual SSRF defence here: it refuses scheme prefixes, paths, quoting and shell metas, so a malicious host value can't redirect requests off-host. PBS hosts are per-customer config (`pbs.internal`, `10.x.x.x`) — operator-side FQDN allowlists can't work in this surface. 13/13 smoke cases pass (FQDN / short / IPv4 / IPv6 / hyphens accepted; empty / scheme / paths / shell-metas / whitespace rejected). Codebase sweep done — no other placeholder-allowlist patterns elsewhere (`api/vms.py:_validate_host` and `utils/sanitization.py:validate_hostname` are pure format checks; `.ssh_ws_server.py:allowed_hosts` is built dynamically per-request from cluster_host + node_ips; `url_security.py` scheme-allowlist has sensible defaults).",
@@ -73,6 +73,7 @@
7373
"images/sponsors/sponsor3-mark.png",
7474
"images/sponsors/sponsor3.png",
7575
"images/sponsors/uvensys.png",
76+
"images/sponsors/datimo.png",
7677
"misc/grafana/README.md",
7778
"misc/grafana/pegaprox_grafana_dashboard_v1.0.json",
7879
"misc/grafana/pegaprox_grafana_dashboard_v1.1.json",

web/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2978,7 +2978,7 @@
29782978
const API_URL=window.location.origin+'/api';// const API_URL = 'http://localhost:5000/api'; // local dev
29792979
// const API_URL = 'https://pegaprox.internal/api' // old staging
29802980
// NS: Central version constant - keep in sync with backend PEGAPROX_VERSION
2981-
const PEGAPROX_VERSION="Beta 0.9.12.2";const DEBUG=false;// set true for verbose logging
2981+
const PEGAPROX_VERSION="Beta 0.9.12.3";const DEBUG=false;// set true for verbose logging
29822982
// NS: global time formatting — reads user pref from localStorage
29832983
function fmtDate(d,opts={}){if(!d)return'';const dt=d instanceof Date?d:new Date(typeof d==='number'&&d<1e12?d*1000:d);if(isNaN(dt))return'';const h12=localStorage.getItem('pegaprox-time-format')==='12h';const defaults={year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:h12};return dt.toLocaleString(undefined,{...defaults,...opts});}function fmtTime(d){if(!d)return'';const dt=d instanceof Date?d:new Date(typeof d==='number'&&d<1e12?d*1000:d);if(isNaN(dt))return'';const h12=localStorage.getItem('pegaprox-time-format')==='12h';return dt.toLocaleTimeString(undefined,{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:h12});}// NS: timezone list for node time config (matches backend get_timezones)
29842984
const TIMEZONES=['UTC','Europe/Berlin','Europe/Vienna','Europe/Zurich','Europe/London','Europe/Paris','Europe/Amsterdam','Europe/Brussels','Europe/Rome','Europe/Madrid','Europe/Warsaw','Europe/Prague','Europe/Budapest','Europe/Stockholm','Europe/Helsinki','Europe/Athens','Europe/Moscow','America/New_York','America/Chicago','America/Denver','America/Los_Angeles','America/Toronto','America/Vancouver','America/Sao_Paulo','America/Mexico_City','Asia/Tokyo','Asia/Shanghai','Asia/Hong_Kong','Asia/Singapore','Asia/Seoul','Asia/Dubai','Asia/Kolkata','Asia/Bangkok','Asia/Jakarta','Australia/Sydney','Australia/Melbourne','Australia/Perth','Pacific/Auckland','Pacific/Fiji','Africa/Cairo','Africa/Johannesburg','Africa/Lagos'];// =====================================================

web/src/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// const API_URL = 'https://pegaprox.internal/api' // old staging
1313

1414
// NS: Central version constant - keep in sync with backend PEGAPROX_VERSION
15-
const PEGAPROX_VERSION = "Beta 0.9.12.2";
15+
const PEGAPROX_VERSION = "Beta 0.9.12.3";
1616
const DEBUG = false; // set true for verbose logging
1717

1818
// NS: global time formatting — reads user pref from localStorage

0 commit comments

Comments
 (0)