Private
Public Access
1
0

fix(proxmox): harden console sessions and firewall rollout safety

Reduce security and reliability risk across the Proxmox extension by removing password exposure from Livewire state, hardening console auth/cookie handling, and improving sanitized error paths. Improve firewall and router performance by adding safer lazy loading, bounded pagination, cross-server compatibility guards, and lower-overhead API workflows.

Made-with: Cursor
This commit is contained in:
2026-03-28 19:34:46 -04:00
parent 9f996035db
commit 68043f0416
39 changed files with 1106 additions and 5013 deletions

View File

@@ -24,6 +24,13 @@ class Component extends BaseComponent
*/
public bool $fastPolling = false;
/**
* Cached Proxmox storage content lookups keyed by storage name.
*
* @var array<string, array<int, array<string, mixed>>>
*/
protected array $storageContentCache = [];
/**
* Mount the component with the server.
*/
@@ -71,7 +78,8 @@ class Component extends BaseComponent
$this->fastPolling = true;
}
} catch (\Throwable $e) {
$this->notify('Action failed: ' . $e->getMessage(), 'error');
report($e);
$this->notify('Action failed. Please try again in a moment.', 'error');
} finally {
$this->loadVmStatus();
}
@@ -113,8 +121,8 @@ class Component extends BaseComponent
return true;
} catch (\Throwable $e) {
// If check fails, allow the action (fail open)
return true;
report($e);
return 'Unable to validate storage quota at this time. Please try again in a moment.';
}
}
@@ -139,6 +147,13 @@ class Component extends BaseComponent
}
$totalSize += $this->extractDiskSize($value);
}
// LXC root filesystem and mount points
elseif ($key === 'rootfs' || preg_match('/^mp(\d+)$/', $key)) {
if ($this->isExcludedDisk($value)) {
continue;
}
$totalSize += $this->extractDiskSize($value);
}
// Check for unused/detached drives
elseif (preg_match('/^unused(\d+)$/', $key)) {
if ($this->isExcludedDisk($value)) {
@@ -215,9 +230,31 @@ class Component extends BaseComponent
$volume = trim($value);
// Extract storage name from volume (format: storage:volume-name)
$storageName = 'cephpool';
if (str_contains($volume, ':')) {
[$storageName, ] = explode(':', $volume, 2);
$volumeParts = explode(':', $volume, 2);
$storageName = trim($volumeParts[0] ?? '');
if ($storageName === '') {
return 0;
}
$storageRows = $this->getStorageContentRows($storageName);
foreach ($storageRows as $item) {
if (isset($item['volid']) && $item['volid'] === $volume && isset($item['size'])) {
return round(((float) $item['size']) / (1024 * 1024 * 1024), 2);
}
}
return 0;
}
/**
* Fetch and memoize storage contents for the active VM.
*
* @return array<int, array<string, mixed>>
*/
protected function getStorageContentRows(string $storageName): array
{
if (array_key_exists($storageName, $this->storageContentCache)) {
return $this->storageContentCache[$storageName];
}
try {
@@ -226,24 +263,13 @@ class Component extends BaseComponent
'/nodes/' . $this->server->node->name . '/storage/' . $storageName . '/content',
data: ['vmid' => $this->server->vm_id]
);
$data = $storageContent->json()['data'] ?? [];
if (is_array($data)) {
foreach ($data as $item) {
if (isset($item['volid']) && $item['volid'] === $volume) {
// Size is in bytes
if (isset($item['size'])) {
return round($item['size'] / (1024 * 1024 * 1024), 2);
}
break;
}
}
}
$rows = $storageContent->json()['data'] ?? [];
$this->storageContentCache[$storageName] = is_array($rows) ? $rows : [];
} catch (\Throwable $e) {
// If we can't fetch size, return 0
$this->storageContentCache[$storageName] = [];
}
return 0;
return $this->storageContentCache[$storageName];
}
/**