github PegaProx/project-pegaprox v1.2.0
PegaProx 1.2.0

6 hours ago

PegaProx 1.2.0 is a security release. It is built on a full AI-assisted penetration test of the whole codebase — 558 findings triaged, every critical and high one verified against the code by hand before anything was changed — plus the tenant work that was queued behind it.

It is a minor release rather than a patch because of the sixteen behaviour changes below. Several of them take something away from a setup that works today. If you run a multi-tenant installation, please read that section before updating.

🔒 Security

One class of defect accounted for most of the findings: a store that fails to load answers with an empty result, and empty was being read as the permissive answer. A tenant table that could not be read handed out every cluster. An unreadable ACL store stopped confining anybody. An unreadable schedule table let a write begin with a DELETE. A failed settings read bound the syslog receiver's unauthenticated port on an instance that had switched it off. All of those now refuse rather than guess.

  • Log injection is neutralised at the sink instead of at seventy-five call sites, so a name carrying escape sequences can no longer repaint an operator's terminal.
  • URLs written into logs have their credentials stripped. For a webhook the URL is the credential, which is what made this worth fixing rather than tidying.
  • SDN object ids are validated before they are used as a path segment. A .. in a zone, vnet, fabric, controller, IPAM or DNS id reached a neighbouring API object on PUT and DELETE.
  • The OIDC discovery address is pinned to the address it resolved to, and a group mapping that matches only on an IdP group's display name now says so in the log.
  • LDAP warns when neither TLS nor STARTTLS is configured. The warning used to sit inside the TLS branch, so the one configuration that needed it was the one that stayed quiet — and both switches default to off.
  • Lists that arrive in a request body are bounded, rate limiters can no longer be grown without bound by the thing they limit, and a managed node can no longer hand us output until we run out of memory.
  • The second-factor probe fails closed on both the login and the re-authentication path, so a database hiccup no longer waves a key-only account through.

⚠️ Behaviour changes

Sixteen, in full. Nothing here is an accident, but all of it will be noticed.

1. OIDC group revocation now takes effect. At the next login, permissions accumulated over time that are no longer covered by an IdP group are dropped. This is the point of the change, but it will be reported as "my permissions are gone".

2. Alert channels require admin.settings to change. alert_webhooks is an installation-wide list while alert.manage is a delegable tenant permission. Creating, changing and deleting a channel now needs admin.settings as well. Reading the masked list stays on alert.manage. Without this, a tenant delegate could redirect the channel another tenant receives its alerts on to their own endpoint.

3. PBS resource-wide actions require every linked cluster. Garbage collection, prune, verify, datastore create and delete, job management and host upgrade now require the caller to hold every cluster linked to that PBS server. Single-tenant installations notice nothing. Where one PBS backs several tenants, these actions become global-administrator territory.

4. PBS keeps linked_clusters when an update omits the field. To clear the link, send an explicit empty list. Previously, omitting it cleared the link — and a PBS without links is visible to every tenant.

5. PBS certificate pinning is enforced when a fingerprint is configured. A stale or wrong fingerprint used to be ignored outside the connection test; now the connection fails. Without a configured fingerprint nothing changes. The current value can be read with the probe button in the PBS dialog.

6. API tokens with their own role are capped at their owner. A token whose owner was demoted, or had a permission withdrawn, now loses that permission too. Automation running under a demoted account's token can stop working — which is the point, but it belongs in an announcement.

7. inherit_role on a VM ACL is checked against the permissions actually granted. A delegate writing ACLs with inherit_role (the default) without holding all ten VM permissions now gets a 403. Either widen the delegate's permissions or use inherit_role=false with an explicit permission list.

8. Drift events and SDN mutations are cluster-wide operations. A user confined to individual VMs (portal, pool, VM ACL) can no longer read the drift event list or create and change cross-cluster vnets. The drift status tile — counts only — stays visible to everyone.

9. Restoring into a new VMID respects the tenant's VMID range. If you have configured a per-tenant range, restores outside it are refused. Without a configured range nothing changes.

10. ESXi delete and rename require vmware.vm.manage. Both routes previously required vmware.vm.power — an account that could switch a VM on and off could therefore delete it. An account with only power rights now needs the additional permission.

11. ESXi keeps linked_clusters when an update omits the field. As with PBS in point 4: send an explicit empty list to clear it.

12. A resource-pool grant no longer opens an ESXi server. The tenant boundary for an ESXi server is now computed without pool reach. Reaching the ESXi inventory through a resource pool on a linked Proxmox cluster no longer works; the cluster's tenant assignment is required.

13. A node reboot now requires node.reboot. The change most likely to be noticed. node.reboot was defined, sat in the tenant_admin template and appeared in the permission editor — and was never checked by any route. A role built with node.update and deliberately without node.reboot still got the reboots. Both the manual and the scheduled rolling update now require it, but only when the run actually reboots (include_reboot); a run without a reboot still works on node.update alone. To do: review roles that hold node.update. Note that include_reboot defaults to ON in a scheduled update — a schedule without the field books a reboot.

14. A node name carried by two clusters no longer filters in syslog. The filter derived a short form from every node name (pve1 from pve1.a.example) and searched hostname = pve1 OR hostname LIKE 'pve1.%'. The second half reached across the tenant boundary: tenant A's pve1 matched tenant B's pve1.b.example. A name carried by more than one cluster is now discarded. Consequence: if you named a node identically in two clusters — pve1 is the Proxmox default — its lines no longer appear in the syslog filter. Use unique names or FQDNs; unique names keep the prefix search.

15. ESXi transfers follow the host-key setting. The ESXi transfers running over SSHFS/scp on the PVE node were hard-wired to accept-new. With Strict Host Keys enabled they now require yes. Consequence: the PVE node needs the ESXi host in its own known_hosts or the migration aborts. That is what the setting means; it was previously ineffective here. Without strict mode nothing changes.

16. Smaller tightenings without pitfalls.

  • An unreadable tenant table yields no clusters rather than all of them. Previously a default-tenant user got the whole environment on a read error, and the invented default tenant was written back, permanently erasing the confinement.
  • admin.api stays inside its own tenant. Listing and revoking tokens was installation-wide.
  • Cluster groups check the token, not its owner. An admin-created token capped at viewer counted as admin there.
  • Node shell credentials only for cluster-wide holders. A caller confined by VM ACL or pool no longer gets root shell access.
  • Template deployment respects the tenant's VMID range and requires cluster-wide standing, like creating a VM.
  • VMware ACL rows no longer act across tenant boundaries, not even with *.
  • Push subscription and inbox are interactive only. An API token can no longer register a delivery endpoint.
  • StarWind installer: custom repository and key URLs are global-admin only. The default source stays open to admin.settings.
  • Docker: PEGAPROX_VERSION pins the image. The default remains latest.

Deliberately not changed

  • Console access for viewers (ESXi). On the Proxmox side the viewer role has always held vm.console, so this is a product decision rather than an inconsistency. Changing it would break everyone who hands out viewer accounts for console access.
  • The drift status tile. Counts only, no configuration diffs. It stays visible to everyone, otherwise it disappears for portal users.
  • TLS defaults for PVE / XCP-ng / LDAP / ESXi. Still False by default. Flipping them breaks every installation with a self-signed certificate. PBS now has certificate pinning instead (point 5); the same pattern is the better road here too.

🏢 Tenants

  • A per-tenant disk ceiling.
  • A per-tenant VMID slice that restore and template deployment both respect.
  • The tenant list behind them.
  • Self-service container creation is serialised per tenant, so two requests can no longer clear the same quota check.

🐛 Fixes

  • ESXi to Proxmox migration never got past VMID allocation and had not worked since 0.9.2.
  • Drift events never reached the configured webhook channels (#815).
  • The client-portal container quota could be skipped, and a failed creation carried on past the failure.
  • A single failed read could rewrite the whole schedule table.
  • The compliance audit report named the tool five releases out of date on every export.
  • SSH credential handling: the gate missed the configuration that trips it most often; a refusal claimed a stored key that may not be there; a token we minted ourselves did not make the password disappear; and root plus the web password are no longer offered to somebody else's hypervisor.
  • Node hardening now checks the node rather than the file we left on it.
  • The CVE scan names what actually failed and measures against the right suite (#825, #827).
  • The V2P path stopped asking a Proxmox node for qemu-utils, plus three other misleading injection messages (#824).
  • Two handlers caught sqlite3's exception classes on SQLCipher connections (#822).
  • A NaN tuning value would have hung an evacuation forever, and a typo in a tuning variable stopped the service booting.
  • The SSL switch now says what it actually does (#638).
  • Rolling updates stop judging an HA migration before the guest has moved (#647).
  • The VNC reconnect rides out more than six seconds, and mints its auth ticket on a node that is actually up.
  • Snapshot API errors surface in toasts instead of vanishing (#812).
  • PBS stopped reporting a refusal as an empty result, and the PBS notification read is guarded the same way as the syslog one (#802, #803).
  • An overtaken syslog request no longer takes the panel back (#802).
  • Cross-cluster replication confirms the replica tag stuck, and says so when it did not (#799).
  • Corporate mass actions work, and the PBS panels say what went wrong (#798, #795).
  • last_login is recorded for OIDC and Entra logins, and only the affected account is persisted (#794).
  • Clusters are labelled by display name everywhere, not the config name (#787).
  • The Add Cluster dialog resets on close, and qcow2 is offered on LVM (#788, #785).
  • The ESXi skip-certificate flag is coerced rather than trusted for truthiness.
  • A pool grant makes the pool's VMs visible (#793).
  • Node maintenance survives a restart when PVE HA accepted it (#720, PR by @hugobugomugo).
  • The full Proxmox boot-order format is parsed instead of the legacy letters alone (#755, PR by @si458).
  • A balancing exclusion is no longer dropped when a cluster enumerates empty.
  • A failed test failover leaves the clone stopped instead of starting it with live NICs.
  • The status page no longer hands database errors to the browser.

🔧 CI

  • The APT repository workflow can no longer be dispatched from an arbitrary branch to reach the signing key.
  • The release version is validated before it reaches a shell.
  • Build jobs no longer carry a token that can write to the repository.

✅ Quality

2040 automated tests (authorization, integration, SSRF, crypto rotation, i18n, frontend invariants) run on every PR. Every security fix in this release ships with a regression test and a counter-proof — a run against the unpatched code, checked to fail for the right reason rather than merely to fail. The release was verified end to end against a live PVE 9.2.10 cluster.

Thank you to everyone who filed, fixed, translated and sponsored along the way. 💚

Thanks in particular to @jostrasser, @Frisch12, @avsdev-cw, @kglowinska, @Drauku, @nvaert1986, @hugobugomugo, @si458, @JanK-96, @Br00dkast, @SpyrosPsarras and @cklabautermann, who filed or fixed the issues closed by this release.

💛 Sponsors

PegaProx is AGPL-3.0 and built in the open. Huge thanks to our Platinum sponsors who keep it moving:

💎 Platinum

Don't miss a new project-pegaprox release

NewReleases is sending notifications on new releases.